1// Copyright 2017 Istio Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// This file describes the abstract model of services (and their instances) as
16// represented in Istio. This model is independent of the underlying platform
17// (Kubernetes, Mesos, etc.). Platform specific adapters found populate the
18// model object with various fields, from the metadata found in the platform.
19// The platform independent proxy code uses the representation in the model to
20// generate the configuration files for the Layer 7 proxy sidecar. The proxy
21// code is specific to individual proxy implementations
22
23package model
24
25import (
26	"bytes"
27	"fmt"
28	"sort"
29	"strconv"
30	"strings"
31	"sync"
32	"time"
33
34	endpoint "github.com/envoyproxy/go-control-plane/envoy/api/v2/endpoint"
35	"github.com/mitchellh/copystructure"
36
37	"istio.io/api/label"
38
39	authn "istio.io/api/authentication/v1alpha1"
40
41	"istio.io/istio/pkg/config/host"
42	"istio.io/istio/pkg/config/labels"
43	"istio.io/istio/pkg/config/protocol"
44	"istio.io/istio/pkg/config/visibility"
45)
46
47// Service describes an Istio service (e.g., catalog.mystore.com:8080)
48// Each service has a fully qualified domain name (FQDN) and one or more
49// ports where the service is listening for connections. *Optionally*, a
50// service can have a single load balancer/virtual IP address associated
51// with it, such that the DNS queries for the FQDN resolves to the virtual
52// IP address (a load balancer IP).
53//
54// E.g., in kubernetes, a service foo is associated with
55// foo.default.svc.cluster.local hostname, has a virtual IP of 10.0.1.1 and
56// listens on ports 80, 8080
57type Service struct {
58	// Attributes contains additional attributes associated with the service
59	// used mostly by mixer and RBAC for policy enforcement purposes.
60	Attributes ServiceAttributes
61
62	// Ports is the set of network ports where the service is listening for
63	// connections
64	Ports PortList `json:"ports,omitempty"`
65
66	// ServiceAccounts specifies the service accounts that run the service.
67	ServiceAccounts []string `json:"serviceAccounts,omitempty"`
68
69	// CreationTime records the time this service was created, if available.
70	CreationTime time.Time `json:"creationTime,omitempty"`
71
72	// Name of the service, e.g. "catalog.mystore.com"
73	Hostname host.Name `json:"hostname"`
74
75	// Address specifies the service IPv4 address of the load balancer
76	Address string `json:"address,omitempty"`
77
78	// Protect concurrent ClusterVIPs read/write
79	Mutex sync.RWMutex
80
81	// ClusterVIPs specifies the service address of the load balancer
82	// in each of the clusters where the service resides
83	ClusterVIPs map[string]string `json:"cluster-vips,omitempty"`
84
85	// Resolution indicates how the service instances need to be resolved before routing
86	// traffic. Most services in the service registry will use static load balancing wherein
87	// the proxy will decide the service instance that will receive the traffic. Service entries
88	// could either use DNS load balancing (i.e. proxy will query DNS server for the IP of the service)
89	// or use the passthrough model (i.e. proxy will forward the traffic to the network endpoint requested
90	// by the caller)
91	Resolution Resolution
92
93	// MeshExternal (if true) indicates that the service is external to the mesh.
94	// These services are defined using Istio's ServiceEntry spec.
95	MeshExternal bool
96}
97
98// Resolution indicates how the service instances need to be resolved before routing
99// traffic.
100type Resolution int
101
102const (
103	// ClientSideLB implies that the proxy will decide the endpoint from its local lb pool
104	ClientSideLB Resolution = iota
105	// DNSLB implies that the proxy will resolve a DNS address and forward to the resolved address
106	DNSLB
107	// Passthrough implies that the proxy should forward traffic to the destination IP requested by the caller
108	Passthrough
109)
110
111// String converts Resolution in to String.
112func (resolution Resolution) String() string {
113	switch resolution {
114	case ClientSideLB:
115		return "ClientSide"
116	case DNSLB:
117		return "DNS"
118	case Passthrough:
119		return "Passthrough"
120	default:
121		return fmt.Sprintf("%d", int(resolution))
122	}
123}
124
125const (
126	// IstioDefaultConfigNamespace constant for default namespace
127	IstioDefaultConfigNamespace = "default"
128
129	// LocalityLabel indicates the region/zone/subzone of an instance. It is used to override the native
130	// registry's value.
131	//
132	// Note: because k8s labels does not support `/`, so we use `.` instead in k8s.
133	LocalityLabel = "istio-locality"
134	// k8s istio-locality label separator
135	k8sSeparator = "."
136)
137
138const (
139	// TLSModeLabelShortname name used for determining endpoint level tls transport socket configuration
140	TLSModeLabelShortname = "tlsMode"
141
142	// DisabledTLSModeLabel implies that this endpoint should receive traffic as is (mostly plaintext)
143	DisabledTLSModeLabel = "disabled"
144
145	// IstioMutualTLSModeLabel implies that the endpoint is ready to receive Istio mTLS connections.
146	IstioMutualTLSModeLabel = "istio"
147
148	// IstioCanonicalServiceLabelName is the name of label for the Istio Canonical Service for a workload instance.
149	IstioCanonicalServiceLabelName = "service.istio.io/canonical-name"
150
151	// IstioCanonicalServiceRevisionLabelName is the name of label for the Istio Canonical Service revision for a workload instance.
152	IstioCanonicalServiceRevisionLabelName = "service.istio.io/canonical-revision"
153)
154
155// Port represents a network port where a service is listening for
156// connections. The port should be annotated with the type of protocol
157// used by the port.
158type Port struct {
159	// Name ascribes a human readable name for the port object. When a
160	// service has multiple ports, the name field is mandatory
161	Name string `json:"name,omitempty"`
162
163	// Port number where the service can be reached. Does not necessarily
164	// map to the corresponding port numbers for the instances behind the
165	// service.
166	Port int `json:"port"`
167
168	// Protocol to be used for the port.
169	Protocol protocol.Instance `json:"protocol,omitempty"`
170}
171
172// PortList is a set of ports
173type PortList []*Port
174
175// TrafficDirection defines whether traffic exists a service instance or enters a service instance
176type TrafficDirection string
177
178const (
179	// TrafficDirectionInbound indicates inbound traffic
180	TrafficDirectionInbound TrafficDirection = "inbound"
181	// TrafficDirectionOutbound indicates outbound traffic
182	TrafficDirectionOutbound TrafficDirection = "outbound"
183
184	// trafficDirectionOutboundSrvPrefix the prefix for a DNS SRV type subset key
185	trafficDirectionOutboundSrvPrefix = string(TrafficDirectionOutbound) + "_"
186	// trafficDirectionInboundSrvPrefix the prefix for a DNS SRV type subset key
187	trafficDirectionInboundSrvPrefix = string(TrafficDirectionInbound) + "_"
188)
189
190// Probe represents a health probe associated with an instance of service.
191type Probe struct {
192	Port *Port  `json:"port,omitempty"`
193	Path string `json:"path,omitempty"`
194}
195
196// ProbeList is a set of probes
197type ProbeList []*Probe
198
199// ServiceInstance represents an individual instance of a specific version
200// of a service. It binds a network endpoint (ip:port), the service
201// description (which is oblivious to various versions) and a set of labels
202// that describe the service version associated with this instance.
203//
204// Since a ServiceInstance has a single IstioEndpoint, which has a single port,
205// multiple ServiceInstances are required to represent a workload that listens
206// on multiple ports.
207//
208// The labels associated with a service instance are unique per a network endpoint.
209// There is one well defined set of labels for each service instance network endpoint.
210//
211// For example, the set of service instances associated with catalog.mystore.com
212// are modeled like this
213//      --> IstioEndpoint(172.16.0.1:8888), Service(catalog.myservice.com), Labels(foo=bar)
214//      --> IstioEndpoint(172.16.0.2:8888), Service(catalog.myservice.com), Labels(foo=bar)
215//      --> IstioEndpoint(172.16.0.3:8888), Service(catalog.myservice.com), Labels(kitty=cat)
216//      --> IstioEndpoint(172.16.0.4:8888), Service(catalog.myservice.com), Labels(kitty=cat)
217type ServiceInstance struct {
218	Service     *Service       `json:"service,omitempty"`
219	ServicePort *Port          `json:"servicePort,omitempty"`
220	Endpoint    *IstioEndpoint `json:"endpoint,omitempty"`
221}
222
223// DeepCopy creates a copy of ServiceInstance.
224func (instance *ServiceInstance) DeepCopy() *ServiceInstance {
225	return &ServiceInstance{
226		Service:  instance.Service.DeepCopy(),
227		Endpoint: instance.Endpoint.DeepCopy(),
228		ServicePort: &Port{
229			Name:     instance.ServicePort.Name,
230			Port:     instance.ServicePort.Port,
231			Protocol: instance.ServicePort.Protocol,
232		},
233	}
234}
235
236// GetLocalityLabelOrDefault returns the locality from the supplied label, or falls back to
237// the supplied default locality if the supplied label is empty. Because Kubernetes
238// labels don't support `/`, we replace "." with "/" in the supplied label as a workaround.
239func GetLocalityLabelOrDefault(label, defaultLabel string) string {
240	if len(label) > 0 {
241		// if there are /'s present we don't need to replace
242		if strings.Contains(label, "/") {
243			return label
244		}
245		// replace "." with "/"
246		return strings.Replace(label, k8sSeparator, "/", -1)
247	}
248	return defaultLabel
249}
250
251// Locality information for an IstioEndpoint
252type Locality struct {
253	// Label for locality on the endpoint. This is a "/" separated string.
254	Label string
255
256	// ClusterID where the endpoint is located
257	ClusterID string
258}
259
260// IstioEndpoint defines a network address (IP:port) associated with an instance of the
261// service. A service has one or more instances each running in a
262// container/VM/pod. If a service has multiple ports, then the same
263// instance IP is expected to be listening on multiple ports (one per each
264// service port). Note that the port associated with an instance does not
265// have to be the same as the port associated with the service. Depending
266// on the network setup (NAT, overlays), this could vary.
267//
268// For e.g., if catalog.mystore.com is accessible through port 80 and 8080,
269// and it maps to an instance with IP 172.16.0.1, such that connections to
270// port 80 are forwarded to port 55446, and connections to port 8080 are
271// forwarded to port 33333,
272//
273// then internally, we have two two endpoint structs for the
274// service catalog.mystore.com
275//  --> 172.16.0.1:54546 (with ServicePort pointing to 80) and
276//  --> 172.16.0.1:33333 (with ServicePort pointing to 8080)
277//
278// TODO: Investigate removing ServiceInstance entirely.
279type IstioEndpoint struct {
280	// Labels points to the workload or deployment labels.
281	Labels labels.Instance
282
283	// Address is the address of the endpoint, using envoy proto.
284	Address string
285
286	// ServicePortName tracks the name of the port, this is used to select the IstioEndpoint by service port.
287	ServicePortName string
288
289	// UID identifies the workload, for telemetry purpose.
290	UID string
291
292	// EnvoyEndpoint is a cached LbEndpoint, converted from the data, to
293	// avoid recomputation
294	EnvoyEndpoint *endpoint.LbEndpoint
295
296	// ServiceAccount holds the associated service account.
297	ServiceAccount string
298
299	// Network holds the network where this endpoint is present
300	Network string
301
302	// The locality where the endpoint is present.
303	Locality Locality
304
305	// EndpointPort is the port where the workload is listening, can be different
306	// from the service port.
307	EndpointPort uint32
308
309	// The load balancing weight associated with this endpoint.
310	LbWeight uint32
311
312	// TLSMode endpoint is injected with istio sidecar and ready to configure Istio mTLS
313	TLSMode string
314}
315
316// ServiceAttributes represents a group of custom attributes of the service.
317type ServiceAttributes struct {
318	// ServiceRegistry indicates the backing service registry system where this service
319	// was sourced from.
320	// TODO: move the ServiceRegistry type from platform.go to model
321	ServiceRegistry string
322	// Name is "destination.service.name" attribute
323	Name string
324	// Namespace is "destination.service.namespace" attribute
325	Namespace string
326	// UID is "destination.service.uid" attribute
327	UID string
328	// ExportTo defines the visibility of Service in
329	// a namespace when the namespace is imported.
330	ExportTo map[visibility.Instance]bool
331
332	// For Kubernetes platform
333
334	// ClusterExternalAddresses is a mapping between a cluster name and the external
335	// address(es) to access the service from outside the cluster.
336	// Used by the aggregator to aggregate the Attributes.ClusterExternalAddresses
337	// for clusters where the service resides
338	ClusterExternalAddresses map[string][]string
339
340	// ClusterExternalPorts is a mapping between a cluster name and the service port
341	// to node port mappings for a given service. When accessing the service via
342	// node port IPs, we need to use the kubernetes assigned node ports of the service
343	// The port that the user provides in the meshNetworks config is the service port.
344	// We translate that to the appropriate node port here.
345	ClusterExternalPorts map[string]map[uint32]uint32
346}
347
348// ServiceDiscovery enumerates Istio service instances.
349// nolint: lll
350//go:generate counterfeiter -o ../networking/core/v1alpha3/fakes/fake_service_discovery.gen.go --fake-name ServiceDiscovery . ServiceDiscovery
351type ServiceDiscovery interface {
352	// Services list declarations of all services in the system
353	Services() ([]*Service, error)
354
355	// GetService retrieves a service by host name if it exists
356	GetService(hostname host.Name) (*Service, error)
357
358	// InstancesByPort retrieves instances for a service on the given ports with labels that match
359	// any of the supplied labels. All instances match an empty tag list.
360	//
361	// For example, consider an example of catalog.mystore.com:
362	// Instances(catalog.myservice.com, 80) ->
363	//      --> IstioEndpoint(172.16.0.1:8888), Service(catalog.myservice.com), Labels(foo=bar)
364	//      --> IstioEndpoint(172.16.0.2:8888), Service(catalog.myservice.com), Labels(foo=bar)
365	//      --> IstioEndpoint(172.16.0.3:8888), Service(catalog.myservice.com), Labels(kitty=cat)
366	//      --> IstioEndpoint(172.16.0.4:8888), Service(catalog.myservice.com), Labels(kitty=cat)
367	//
368	// Calling Instances with specific labels returns a trimmed list.
369	// e.g., Instances(catalog.myservice.com, 80, foo=bar) ->
370	//      --> IstioEndpoint(172.16.0.1:8888), Service(catalog.myservice.com), Labels(foo=bar)
371	//      --> IstioEndpoint(172.16.0.2:8888), Service(catalog.myservice.com), Labels(foo=bar)
372	//
373	// Similar concepts apply for calling this function with a specific
374	// port, hostname and labels.
375	//
376	// Introduced in Istio 0.8. It is only called with 1 port.
377	// CDS (clusters.go) calls it for building 'dnslb' type clusters.
378	// EDS calls it for building the endpoints result.
379	// Consult istio-dev before using this for anything else (except debugging/tools)
380	InstancesByPort(svc *Service, servicePort int, labels labels.Collection) ([]*ServiceInstance, error)
381
382	// GetProxyServiceInstances returns the service instances that co-located with a given Proxy
383	//
384	// Co-located generally means running in the same network namespace and security context.
385	//
386	// A Proxy operating as a Sidecar will return a non-empty slice.  A stand-alone Proxy
387	// will return an empty slice.
388	//
389	// There are two reasons why this returns multiple ServiceInstances instead of one:
390	// - A ServiceInstance has a single IstioEndpoint which has a single Port.  But a Service
391	//   may have many ports.  So a workload implementing such a Service would need
392	//   multiple ServiceInstances, one for each port.
393	// - A single workload may implement multiple logical Services.
394	//
395	// In the second case, multiple services may be implemented by the same physical port number,
396	// though with a different ServicePort and IstioEndpoint for each.  If any of these overlapping
397	// services are not HTTP or H2-based, behavior is undefined, since the listener may not be able to
398	// determine the intended destination of a connection without a Host header on the request.
399	GetProxyServiceInstances(*Proxy) ([]*ServiceInstance, error)
400
401	GetProxyWorkloadLabels(*Proxy) (labels.Collection, error)
402
403	// ManagementPorts lists set of management ports associated with an IPv4 address.
404	// These management ports are typically used by the platform for out of band management
405	// tasks such as health checks, etc. In a scenario where the proxy functions in the
406	// transparent mode (traps all traffic to and from the service instance IP address),
407	// the configuration generated for the proxy will not manipulate traffic destined for
408	// the management ports
409	ManagementPorts(addr string) PortList
410
411	// WorkloadHealthCheckInfo lists set of probes associated with an IPv4 address.
412	// These probes are used by the platform to identify requests that are performing
413	// health checks.
414	WorkloadHealthCheckInfo(addr string) ProbeList
415
416	// GetIstioServiceAccounts returns a list of service accounts looked up from
417	// the specified service hostname and ports.
418	// Deprecated - service account tracking moved to XdsServer, incremental.
419	GetIstioServiceAccounts(svc *Service, ports []int) []string
420}
421
422// Match returns true if port matches with authentication port selector criteria.
423func (port Port) Match(portSelector *authn.PortSelector) bool {
424	if portSelector == nil {
425		return true
426	}
427	switch portSelector.Port.(type) {
428	case *authn.PortSelector_Name:
429		return portSelector.GetName() == port.Name
430	case *authn.PortSelector_Number:
431		return portSelector.GetNumber() == uint32(port.Port)
432	default:
433		return false
434	}
435}
436
437// GetNames returns port names
438func (ports PortList) GetNames() []string {
439	names := make([]string, 0, len(ports))
440	for _, port := range ports {
441		names = append(names, port.Name)
442	}
443	return names
444}
445
446// Get retrieves a port declaration by name
447func (ports PortList) Get(name string) (*Port, bool) {
448	for _, port := range ports {
449		if port.Name == name {
450			return port, true
451		}
452	}
453	return nil, false
454}
455
456// GetByPort retrieves a port declaration by port value
457func (ports PortList) GetByPort(num int) (*Port, bool) {
458	for _, port := range ports {
459		if port.Port == num && port.Protocol != protocol.UDP {
460			return port, true
461		}
462	}
463	return nil, false
464}
465
466// External predicate checks whether the service is external
467func (s *Service) External() bool {
468	return s.MeshExternal
469}
470
471// Key generates a unique string referencing service instances for a given port and labels.
472// The separator character must be exclusive to the regular expressions allowed in the
473// service declaration.
474// Deprecated
475func (s *Service) Key(port *Port, l labels.Instance) string {
476	// TODO: check port is non nil and membership of port in service
477	return ServiceKey(s.Hostname, PortList{port}, labels.Collection{l})
478}
479
480// ServiceKey generates a service key for a collection of ports and labels
481// Deprecated
482//
483// Interface wants to turn `Name` into `fmt.Stringer`, completely defeating the purpose of the type alias.
484// nolint: interfacer
485func ServiceKey(hostname host.Name, servicePorts PortList, labelsList labels.Collection) string {
486	// example: name.namespace|http|env=prod;env=test,version=my-v1
487	var buffer bytes.Buffer
488	buffer.WriteString(string(hostname))
489	np := len(servicePorts)
490	nt := len(labelsList)
491
492	if nt == 1 && labelsList[0] == nil {
493		nt = 0
494	}
495
496	if np == 0 && nt == 0 {
497		return buffer.String()
498	} else if np == 1 && nt == 0 && servicePorts[0].Name == "" {
499		return buffer.String()
500	} else {
501		buffer.WriteString("|")
502	}
503
504	if np > 0 {
505		ports := make([]string, np)
506		for i := 0; i < np; i++ {
507			ports[i] = servicePorts[i].Name
508		}
509		sort.Strings(ports)
510		for i := 0; i < np; i++ {
511			if i > 0 {
512				buffer.WriteString(",")
513			}
514			buffer.WriteString(ports[i])
515		}
516	}
517
518	if nt > 0 {
519		buffer.WriteString("|")
520		l := make([]string, nt)
521		for i := 0; i < nt; i++ {
522			l[i] = labelsList[i].String()
523		}
524		sort.Strings(l)
525		for i := 0; i < nt; i++ {
526			if i > 0 {
527				buffer.WriteString(";")
528			}
529			buffer.WriteString(l[i])
530		}
531	}
532	return buffer.String()
533}
534
535// ParseServiceKey is the inverse of the Service.String() method
536// Deprecated
537func ParseServiceKey(s string) (hostname host.Name, ports PortList, lc labels.Collection) {
538	parts := strings.Split(s, "|")
539	hostname = host.Name(parts[0])
540
541	var names []string
542	if len(parts) > 1 {
543		names = strings.Split(parts[1], ",")
544	} else {
545		names = []string{""}
546	}
547
548	for _, name := range names {
549		ports = append(ports, &Port{Name: name})
550	}
551
552	if len(parts) > 2 && len(parts[2]) > 0 {
553		for _, tag := range strings.Split(parts[2], ";") {
554			lc = append(lc, labels.Parse(tag))
555		}
556	}
557	return
558}
559
560// BuildSubsetKey generates a unique string referencing service instances for a given service name, a subset and a port.
561// The proxy queries Pilot with this key to obtain the list of instances in a subset.
562func BuildSubsetKey(direction TrafficDirection, subsetName string, hostname host.Name, port int) string {
563	return string(direction) + "|" + strconv.Itoa(port) + "|" + subsetName + "|" + string(hostname)
564}
565
566// BuildDNSSrvSubsetKey generates a unique string referencing service instances for a given service name, a subset and a port.
567// The proxy queries Pilot with this key to obtain the list of instances in a subset.
568// This is used only for the SNI-DNAT router. Do not use for other purposes.
569// The DNS Srv format of the cluster is also used as the default SNI string for Istio mTLS connections
570func BuildDNSSrvSubsetKey(direction TrafficDirection, subsetName string, hostname host.Name, port int) string {
571	return string(direction) + "_." + strconv.Itoa(port) + "_." + subsetName + "_." + string(hostname)
572}
573
574// IsValidSubsetKey checks if a string is valid for subset key parsing.
575func IsValidSubsetKey(s string) bool {
576	return strings.Count(s, "|") == 3
577}
578
579// ParseSubsetKey is the inverse of the BuildSubsetKey method
580func ParseSubsetKey(s string) (direction TrafficDirection, subsetName string, hostname host.Name, port int) {
581	var parts []string
582	dnsSrvMode := false
583	// This could be the DNS srv form of the cluster that uses outbound_.port_.subset_.hostname
584	// Since we do not want every callsite to implement the logic to differentiate between the two forms
585	// we add an alternate parser here.
586	if strings.HasPrefix(s, trafficDirectionOutboundSrvPrefix) ||
587		strings.HasPrefix(s, trafficDirectionInboundSrvPrefix) {
588		parts = strings.SplitN(s, ".", 4)
589		dnsSrvMode = true
590	} else {
591		parts = strings.Split(s, "|")
592	}
593
594	if len(parts) < 4 {
595		return
596	}
597
598	direction = TrafficDirection(strings.TrimSuffix(parts[0], "_"))
599	port, _ = strconv.Atoi(strings.TrimSuffix(parts[1], "_"))
600	subsetName = parts[2]
601
602	if dnsSrvMode {
603		subsetName = strings.TrimSuffix(parts[2], "_")
604	}
605
606	hostname = host.Name(parts[3])
607	return
608}
609
610// GetServiceAddressForProxy returns a Service's IP address specific to the cluster where the node resides
611func (s *Service) GetServiceAddressForProxy(node *Proxy) string {
612	s.Mutex.RLock()
613	defer s.Mutex.RUnlock()
614	if node.ClusterID != "" && s.ClusterVIPs[node.ClusterID] != "" {
615		return s.ClusterVIPs[node.ClusterID]
616	}
617	return s.Address
618}
619
620// GetTLSModeFromEndpointLabels returns the value of the label
621// security.istio.io/tlsMode if set. Do not return Enums or constants
622// from this function as users could provide values other than istio/disabled
623// and apply custom transport socket matchers here.
624func GetTLSModeFromEndpointLabels(labels map[string]string) string {
625	if labels != nil {
626		if val, exists := labels[label.TLSMode]; exists {
627			return val
628		}
629	}
630	return DisabledTLSModeLabel
631}
632
633// DeepCopy creates a clone of Service.
634// TODO : See if there is any efficient alternative to this function - copystructure can not be used as is because
635// Service has sync.RWMutex that can not be copied.
636func (s *Service) DeepCopy() *Service {
637	attrs := copyInternal(s.Attributes)
638	ports := copyInternal(s.Ports)
639	accounts := copyInternal(s.ServiceAccounts)
640	clusterVIPs := copyInternal(s.ClusterVIPs)
641
642	return &Service{
643		Attributes:      attrs.(ServiceAttributes),
644		Ports:           ports.(PortList),
645		ServiceAccounts: accounts.([]string),
646		CreationTime:    s.CreationTime,
647		Hostname:        s.Hostname,
648		Address:         s.Address,
649		ClusterVIPs:     clusterVIPs.(map[string]string),
650		Resolution:      s.Resolution,
651		MeshExternal:    s.MeshExternal,
652	}
653}
654
655// DeepCopy creates a clone of IstioEndpoint.
656func (ep *IstioEndpoint) DeepCopy() *IstioEndpoint {
657	return copyInternal(ep).(*IstioEndpoint)
658}
659
660func copyInternal(v interface{}) interface{} {
661	copied, err := copystructure.Copy(v)
662	if err != nil {
663		// There are 2 locations where errors are generated in copystructure.Copy:
664		//  * The reflection walk over the structure fails, which should never happen
665		//  * A configurable copy function returns an error. This is only used for copying times, which never returns an error.
666		// Therefore, this should never happen
667		panic(err)
668	}
669	return copied
670}
671