1package consul 2 3import ( 4 "fmt" 5 "net" 6 "strconv" 7 8 "github.com/hashicorp/consul/api" 9 "github.com/hashicorp/nomad/helper" 10 "github.com/hashicorp/nomad/nomad/structs" 11) 12 13// newConnect creates a new Consul AgentServiceConnect struct based on a Nomad 14// Connect struct. If the nomad Connect struct is nil, nil will be returned to 15// disable Connect for this service. 16func newConnect(serviceId string, serviceName string, nc *structs.ConsulConnect, networks structs.Networks, ports structs.AllocatedPorts) (*api.AgentServiceConnect, error) { 17 switch { 18 case nc == nil: 19 // no connect stanza means there is no connect service to register 20 return nil, nil 21 22 case nc.IsGateway(): 23 // gateway settings are configured on the service block on the consul side 24 return nil, nil 25 26 case nc.IsNative(): 27 // the service is connect native 28 return &api.AgentServiceConnect{Native: true}, nil 29 30 case nc.HasSidecar(): 31 // must register the sidecar for this service 32 if nc.SidecarService.Port == "" { 33 nc.SidecarService.Port = fmt.Sprintf("%s-%s", structs.ConnectProxyPrefix, serviceName) 34 } 35 sidecarReg, err := connectSidecarRegistration(serviceId, nc.SidecarService, networks, ports) 36 if err != nil { 37 return nil, err 38 } 39 return &api.AgentServiceConnect{SidecarService: sidecarReg}, nil 40 41 default: 42 // a non-nil but empty connect block makes no sense 43 return nil, fmt.Errorf("Connect configuration empty for service %s", serviceName) 44 } 45} 46 47// newConnectGateway creates a new Consul AgentServiceConnectProxyConfig struct based on 48// a Nomad Connect struct. If the Nomad Connect struct does not contain a gateway, nil 49// will be returned as this service is not a gateway. 50func newConnectGateway(serviceName string, connect *structs.ConsulConnect) *api.AgentServiceConnectProxyConfig { 51 if !connect.IsGateway() { 52 return nil 53 } 54 55 var envoyConfig map[string]interface{} 56 57 // Populate the envoy configuration from the gateway.proxy stanza, if 58 // such configuration is provided. 59 if proxy := connect.Gateway.Proxy; proxy != nil { 60 envoyConfig = make(map[string]interface{}) 61 62 if len(proxy.EnvoyGatewayBindAddresses) > 0 { 63 envoyConfig["envoy_gateway_bind_addresses"] = proxy.EnvoyGatewayBindAddresses 64 } 65 66 if proxy.EnvoyGatewayNoDefaultBind { 67 envoyConfig["envoy_gateway_no_default_bind"] = true 68 } 69 70 if proxy.EnvoyGatewayBindTaggedAddresses { 71 envoyConfig["envoy_gateway_bind_tagged_addresses"] = true 72 } 73 74 if proxy.EnvoyDNSDiscoveryType != "" { 75 envoyConfig["envoy_dns_discovery_type"] = proxy.EnvoyDNSDiscoveryType 76 } 77 78 if proxy.ConnectTimeout != nil { 79 envoyConfig["connect_timeout_ms"] = proxy.ConnectTimeout.Milliseconds() 80 } 81 82 if len(proxy.Config) > 0 { 83 for k, v := range proxy.Config { 84 envoyConfig[k] = v 85 } 86 } 87 } 88 89 return &api.AgentServiceConnectProxyConfig{Config: envoyConfig} 90} 91 92func connectSidecarRegistration(serviceId string, css *structs.ConsulSidecarService, networks structs.Networks, ports structs.AllocatedPorts) (*api.AgentServiceRegistration, error) { 93 if css == nil { 94 // no sidecar stanza means there is no sidecar service to register 95 return nil, nil 96 } 97 98 cMapping, err := connectPort(css.Port, networks, ports) 99 if err != nil { 100 return nil, err 101 } 102 103 proxy, err := connectSidecarProxy(css.Proxy, cMapping.To, networks) 104 if err != nil { 105 return nil, err 106 } 107 108 // if the service has a TCP check that's failing, we need an alias to 109 // ensure service discovery excludes this sidecar from queries 110 // (ex. in the case of Connect upstreams) 111 checks := api.AgentServiceChecks{{ 112 Name: "Connect Sidecar Aliasing " + serviceId, 113 AliasService: serviceId, 114 }} 115 if !css.DisableDefaultTCPCheck { 116 checks = append(checks, &api.AgentServiceCheck{ 117 Name: "Connect Sidecar Listening", 118 TCP: net.JoinHostPort(cMapping.HostIP, strconv.Itoa(cMapping.Value)), 119 Interval: "10s", 120 }) 121 } 122 123 return &api.AgentServiceRegistration{ 124 Tags: helper.CopySliceString(css.Tags), 125 Port: cMapping.Value, 126 Address: cMapping.HostIP, 127 Proxy: proxy, 128 Checks: checks, 129 }, nil 130} 131 132func connectSidecarProxy(proxy *structs.ConsulProxy, cPort int, networks structs.Networks) (*api.AgentServiceConnectProxyConfig, error) { 133 if proxy == nil { 134 proxy = new(structs.ConsulProxy) 135 } 136 137 expose, err := connectProxyExpose(proxy.Expose, networks) 138 if err != nil { 139 return nil, err 140 } 141 142 return &api.AgentServiceConnectProxyConfig{ 143 LocalServiceAddress: proxy.LocalServiceAddress, 144 LocalServicePort: proxy.LocalServicePort, 145 Config: connectProxyConfig(proxy.Config, cPort), 146 Upstreams: connectUpstreams(proxy.Upstreams), 147 Expose: expose, 148 }, nil 149} 150 151func connectProxyExpose(expose *structs.ConsulExposeConfig, networks structs.Networks) (api.ExposeConfig, error) { 152 if expose == nil { 153 return api.ExposeConfig{}, nil 154 } 155 156 paths, err := connectProxyExposePaths(expose.Paths, networks) 157 if err != nil { 158 return api.ExposeConfig{}, err 159 } 160 161 return api.ExposeConfig{ 162 Checks: false, 163 Paths: paths, 164 }, nil 165} 166 167func connectProxyExposePaths(in []structs.ConsulExposePath, networks structs.Networks) ([]api.ExposePath, error) { 168 if len(in) == 0 { 169 return nil, nil 170 } 171 172 paths := make([]api.ExposePath, len(in)) 173 for i, path := range in { 174 if _, exposedPort, err := connectExposePathPort(path.ListenerPort, networks); err != nil { 175 return nil, err 176 } else { 177 paths[i] = api.ExposePath{ 178 ListenerPort: exposedPort, 179 Path: path.Path, 180 LocalPathPort: path.LocalPathPort, 181 Protocol: path.Protocol, 182 ParsedFromCheck: false, 183 } 184 } 185 } 186 return paths, nil 187} 188 189func connectUpstreams(in []structs.ConsulUpstream) []api.Upstream { 190 if len(in) == 0 { 191 return nil 192 } 193 194 upstreams := make([]api.Upstream, len(in)) 195 for i, upstream := range in { 196 upstreams[i] = api.Upstream{ 197 DestinationName: upstream.DestinationName, 198 LocalBindPort: upstream.LocalBindPort, 199 Datacenter: upstream.Datacenter, 200 LocalBindAddress: upstream.LocalBindAddress, 201 } 202 } 203 return upstreams 204} 205 206func connectProxyConfig(cfg map[string]interface{}, port int) map[string]interface{} { 207 if cfg == nil { 208 cfg = make(map[string]interface{}) 209 } 210 cfg["bind_address"] = "0.0.0.0" 211 cfg["bind_port"] = port 212 return cfg 213} 214 215func connectNetworkInvariants(networks structs.Networks) error { 216 if n := len(networks); n != 1 { 217 return fmt.Errorf("Connect only supported with exactly 1 network (found %d)", n) 218 } 219 return nil 220} 221 222// connectPort returns the network and port for the Connect proxy sidecar 223// defined for this service. An error is returned if the network and port 224// cannot be determined. 225func connectPort(portLabel string, networks structs.Networks, ports structs.AllocatedPorts) (structs.AllocatedPortMapping, error) { 226 if err := connectNetworkInvariants(networks); err != nil { 227 return structs.AllocatedPortMapping{}, err 228 } 229 mapping, ok := ports.Get(portLabel) 230 if !ok { 231 mapping = networks.Port(portLabel) 232 if mapping.Value > 0 { 233 return mapping, nil 234 } 235 return structs.AllocatedPortMapping{}, fmt.Errorf("No port of label %q defined", portLabel) 236 } 237 return mapping, nil 238} 239 240// connectExposePathPort returns the port for the exposed path for the exposed 241// proxy path. 242func connectExposePathPort(portLabel string, networks structs.Networks) (string, int, error) { 243 if err := connectNetworkInvariants(networks); err != nil { 244 return "", 0, err 245 } 246 247 mapping := networks.Port(portLabel) 248 if mapping.Value == 0 { 249 return "", 0, fmt.Errorf("No port of label %q defined", portLabel) 250 } 251 252 return mapping.HostIP, mapping.Value, nil 253} 254