├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── SECURITY.md ├── Third-Party-Notices.txt ├── deploy ├── Linux_Reverse_Proxy.md └── reverseproxy.yml ├── deploycontainer.cmd ├── deploycontainer.sh ├── gateway ├── Dockerfile.windows ├── Dockerfile.windows.1709 ├── Program.cs ├── config.template.json ├── deploycontainer.cmd ├── startup.cmd └── startup.csproj ├── service-fabric-reverse-proxy.sln └── sfintegration ├── .vscode ├── launch.json └── tasks.json ├── Controllers └── v1.cs ├── Dockerfile ├── Dockerfile.windows ├── EnableResolveNotifications.cs ├── Program.cs ├── ServiceData.cs ├── Startup.cs ├── appsettings.Development.json ├── appsettings.json ├── config.gateway.json ├── config.json ├── config.secure.json ├── config.windows.json ├── config.windows.secure.json ├── envoymodels ├── EnvoyAccessLogConfig.cs ├── EnvoyCluster.cs ├── EnvoyFilterConfig.cs ├── EnvoyHTTPFilterConfig.cs ├── EnvoyHTTPListenerFilter.cs ├── EnvoyHost.cs ├── EnvoyHostTags.cs ├── EnvoyListener.cs ├── EnvoyListenerFilter.cs ├── EnvoyRDSConfig.cs ├── EnvoyRetryPolicy.cs ├── EnvoyRoute.cs ├── EnvoyTCPFilterConfig.cs ├── EnvoyTCPListenerFilter.cs ├── EnvoyTCPRoute.cs ├── EnvoyTCPRouteConfig.cs ├── EnvoyVirtualHost.cs ├── EvoyOutlierDetection.cs └── envoyclustersslcontext.cs ├── models ├── GatewayDestination.cs ├── GatewayProperties.cs ├── HttpConfig.cs ├── HttpHostNameConfig.cs ├── HttpRouteConfig.cs ├── HttpRouteMatchHeader.cs ├── HttpRouteMatchPath.cs ├── HttpRouteMatchRule.cs ├── NetworkRef.cs └── TcpConfig.cs ├── sfintegration.csproj ├── startup.cmd └── startup.sh /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | 290 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | services: 3 | - docker 4 | os: linux 5 | sudo: required 6 | mono: none 7 | dotnet: 2.1.402 8 | branches: 9 | only: 10 | - master 11 | - sridmad 12 | install: 13 | - export DOTNET_CLI_TELEMETRY_OPTOUT=1 14 | before_script: 15 | - chmod a+x ./deploycontainer.sh 16 | script: 17 | - ./deploycontainer.sh $TRAVIS_BRANCH $TRAVIS_COMMIT $DOCKER_USERNAME $DOCKER_PASSWORD $TRAVIS_TAG 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Service Fabric Reverse Proxy 2 | 3 | ## Contributing 4 | 5 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 6 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 7 | the rights to use your contribution. For details, visit https://cla.microsoft.com. 8 | 9 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide 10 | a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions 11 | provided by the bot. You will only need to do this once across all repos using our CLA. 12 | 13 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 14 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 15 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 16 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /Third-Party-Notices.txt: -------------------------------------------------------------------------------- 1 | Do Not Translate or Localize 2 | 3 | This file is based on or incorporates material from the projects listed below (Third Party IP). The original copyright notice and the license under which Microsoft received such Third Party IP, are set forth below. Such licenses and notices are provided for informational purposes only. Microsoft licenses the Third Party IP to you under the licensing terms for the Microsoft product. Microsoft reserves all other rights not expressly granted under this agreement, whether by implication, estoppel or otherwise. 4 | 5 | Envoy 6 | Copyright 2016-2018 Envoy Project Authors 7 | Licensed under Apache License 2.0. 8 | Apache 2.0 License 9 | Licensed under the Apache License, Version 2.0 (the License); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 10 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS-IS" BASES, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for specific language governing permissions and limitations under the License. 12 | 13 | dumb-init 14 | The MIT License (MIT) 15 | Copyright (c) 2015 Yelp, Inc. 16 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 17 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 21 | Upstream-Name: dumb-init 22 | Source: https://github.com/Yelp/dumb-init/ 23 | Files: * 24 | Copyright: 2015, 2016 Yelp, Inc. 25 | License: Expat 26 | Files: debian/* 27 | Copyright: 2015, 2016 Yelp, Inc. | 2016 Dmitry Smirnov 28 | License: Expat 29 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 30 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | -------------------------------------------------------------------------------- /deploy/Linux_Reverse_Proxy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Reverse Proxy for Linux Service Fabric Clusters 3 | ... 4 | 5 | Windows Service Fabric Clusters have a built in Reverse proxy that makes service discovery and communication easy. We not have a Reverse Proxy for Linux to bring the same benefits to Linux Clusters. 6 | 7 | Reverse Proxy for Linux is different from the Reverse Proxy for Windows 8 | in multiple ways. 9 | 10 | ## Built using OSS Envoy Proxy 11 | When we were porting Service Fabric to Linux, we evaluated existing reverse proxies in OSS as opposed to porting our own. We have many different criteria in this evaluation and based on those we decided to Adopt Envoy Proxy. We are working in parallel to bring Envoy Proxy based Reverse Proxy to Windows Clusters. 12 | 13 | ## Deployment 14 | Reverse Proxy on Linux can be deployed like any other application in the cluster. Enabling Reverse proxy requires deploying an application to the cluster and other applications communicating with it. There is no requirement to enable it at cluster deployment time neither is there a need to upgrade the cluster to enable it after the fact. Updating the reverse proxy requires upgrading the Reverse Proxy application. 15 | 16 | Envoy Proxy and Service Fabric integration code is bundled into a Docker container Image on Docker Hub. A Docker Compose file is used to deploy it to cluster 17 | 18 | ## Routing parameters as headers 19 | Windows based Reverse Proxy required including advanced routing information either in the url and/or as query parameters. This resulted in mixing Service specific information with routing information. Starting with Reverse Proxy for Linux we are moving to using headers for specifying routing information. 20 | 21 | ## Customizing Reverse Proxy 22 | ### Reverse Proxy and Service Fabric communication 23 | Reverse Proxy must discover information about the services that are executing in the cluster and track any changes in the cluster. Service Fabric exposes discovery APIs through a secure endpoint that Reverse Proxy communicates with for discovering the information. 24 | 25 | To setup communication with the Service Fabric endpoint, Revers Proxy must provide a Client Certificate and validate the certificate provided by Management endpoint. 26 | 27 | Information about the certificates can be specified to Reverse proxy using the following environment variables in Docker Compose file. 28 | 29 | + **SF_ClientCertCommonName**: Common name of the Client Certificate(s) that Reverse Proxy should present to Service Fabric endpoint. Presence of this environment variable indicates a secure cluster. 30 | 31 | *Required*: For secure cluster. 32 | 33 | + **SF_ClientCertIssuerThumbprints**: Comma separated list of thumbprint of the certificate(s) specified in SF_ClientCertCommonName. 34 | 35 | *Optional*: Required if the SF_ClientCertCommonName is self-signed. 36 | 37 | Information about the certificates that Service Fabric uses that Reverse 38 | Proxy should validate. 39 | 40 | + **SF_ClusterCertCommonNames**: Comma separated list of common name and alternate common names of the certificate(s) used by Service Fabric to secure the cluster. 41 | 42 | *Optional*: This is optional if cluster uses the same Certificate as Cluster and Client Certificate. If not specified and cluster is secure, uses the value specified for SF_ClientCertCommonName. 43 | 44 | + **SF_ClusterCertIssuerThumbprints**: Comma separated list of thumbprints of Cluster certificate(s). 45 | 46 | *Optional*: Required if the name(s) specified in SF_ClusterCertCommonNames are self-signed certificates. If not specified and cluster is secure, uses the values specified for SF_ClientCertIssuerThumbprints. 47 | 48 | ### Setting that control request handling 49 | The main functionality of a Reverse Proxy is to receive requests from clients and route them to an upstream service based on routing information. Following variables in Docker Compose file controls various settings control how requests are handled. 50 | 51 | + **HttpRequestConnectTimeoutMs**: Timeout for new network connections specified in milliseconds. 52 | 53 | *Optional*: If not specified, will use default value of 5000ms is used. 54 | 55 | + **DefaultHttpRequestTimeoutMs**: Timeout for the request specified in milliseconds. 56 | 57 | *Optional*: If not specified, will use default value of 120000ms is used. 58 | 59 | + **RemoveServiceResponseHeaders**: A comma separated list of headers to remove from Service Response before returning the response to client. 60 | 61 | *Optional*: If not specified, no headers are removed from response. 62 | 63 | ### Reverse Proxy listener to handle https connections 64 | Reverse proxy needs a certificate and its private key in order to secure the listener. 65 | 66 | + **ReverseProxyCertThumbprint**: Thumbprint of the certificate. 67 | 68 | If the certificate is specified in cluster manifest, Service Fabric ensures that the certificate is copied to node and put under /var/lib/sfcerts. Otherwise, place following files under /var/lib/sfcerts on each node in the cluster. 69 | + .crt 70 | + .prv 71 | 72 | In addition, set the following environment variable to enable secure listener. 73 | 74 | + **UseHttps**: Specifies whether Reverse proxy listener should secure all communications related to request handling. 75 | 76 | ### Specifying certificate for Reverse Proxy to communicate with a secure upstream service 77 | 78 | + **ServiceCertificateHash**: Thumbprint of the certificate that is presented by a secure Service. 79 | 80 | + **ServiceCertificateAlternateNames**: Comma separated list of alternate names. Reverse Proxy will validate that one of the names matches one of the server certificate’s subject alternate names. 81 | 82 | ## Docker Compose file contents 83 | By default, the Docker Compose file has the following settings. 84 | + Service Name: Reverse_Proxy_Service (Do not change) 85 | 86 | + Instance Count: -1 i.e. runs an instance of the service on every node in the cluster (Do not change) 87 | 88 | + Image name: path to Docker image on Docker hub (Do not change) 89 | 90 | + Volumes: Maps the folder with certificates inside the container. (Do not change) 91 | 92 | + Environment variables: none set. Add the variables appropriate for the cluster 93 | 94 | + Port: 19081 95 | 96 | + To change the port that Reverse proxy listens on, say **20001**, change ports section to **20001**:19081 97 | 98 | + Make sure that the second value specified in the ports section is always 19081 99 | 100 | ## Deploying Reverse Proxy 101 | + Make sure that Service Fabric CLI is installed. [SFCtl](https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-cli) 102 | 103 | + Login azure using CLI 104 | 105 | + Connect to Service Fabric Cluster 106 | 107 | + sfctl cluster select –endpoint <endpoint of Service Fabric cluster> 108 | 109 | + Download and modify Docker Compose file 110 | 111 | + Deploy Reverse Proxy to Cluster 112 | 113 | + sfctl compose create --deployment-name Reverse_Proxy_Application --file-path ./reverseproxy.yml 114 | 115 | + Remove Reverse Proxy from cluster 116 | 117 | + sfctl compose remove --deployment-name Reverse_Proxy_Application 118 | 119 | ## Reverse Proxy URL Format and Headers 120 | Following is the general format of to use Reverse proxy to route requests to a service. 121 | 122 | http(s)://localhost:reverse_proxy_port/application_name/service_name/… 123 | 124 | Headers to control routing 125 | 126 | - PartitionKey – Integer key. Reverse Proxy will route the request to the Stateful partition that handles the partition key 127 | 128 | - ListenerName – Listener name. Name of the listener in the service that the request has to be routed to 129 | 130 | ## Using Reverse Proxy 131 | Following assumes that Reverse Proxy listens on port 19081 (default setting in Docker Compose file) and listening on http. Change the port number to match the value specified in Compose file if it has been changed. If using a https listener, change http to https in the following examples. 132 | 133 | Reverse proxy will listen on the following address on all the nodes in the cluster. 134 | 135 | - or 136 | 137 | To send request to a service with name fabric:/applicationname/servicename and call API /api/values through Reverse Proxy, create url as follows 138 | 139 | - 140 | 141 | Routing to Listener with Name ServiceEndpoint1 in service fabric:/applicationname/servicename use the following. 142 | 143 | - URL: 144 | 145 | - Set following headers 146 | 147 | - ListenerName: ServiceEndpoint1 148 | 149 | Routing to partition that handles data with Partition Key == 1 150 | - URL: 151 | 152 | - Set following headers 153 | 154 | - PartitionKey: 1 155 | 156 | Routing to partition that handles data with Partition Key == 1 and Listener named ServiceEndpoint1 157 | - URL: 158 | 159 | - Set following headers 160 | 161 | - PartitionKey: 1 162 | - ListenerName: ServiceEndpoint1 163 | 164 | ## LAD 3.0 to collects logs 165 | All logs from reverse proxy are stored in /var/log/sfreverseproxy folder in the container. 166 | 167 |   168 | 1. Perform an ARM template update to remove LAD 2.3: i.e Rremove following section from template if present and deploy. LAD 2.3 does not have the capability to upload files to storage. If LAD 2.3 is not installed, skip this step. 169 | 170 | ```json  171 | { 172 | "properties": { 173 | "publisher": "Microsoft.OSTCExtensions", 174 | "type": "LinuxDiagnostic", 175 | "typeHandlerVersion": "2.3", 176 | "autoUpgradeMinorVersion": true, 177 | "settings": { 178 | "xmlCfg": "", 179 | "StorageAccount": "" 180 | } 181 | }, 182 | "name": "" 183 | } 184 | ``` 185 | 2. Create new storage account (It is recommended). 186 | 187 | 3. Generate SAS key for the new storage account using the following. Alternatively, this can also be generated through portal. 188 | 189 | ```bash 190 | az login 191 | az account set --subscription 192 | az storage account generate-sas --account-name newstorageaccountname --expiry 9999-12-31T23:59Z --permissions wlacu --resource-types co --services bt -o tsv 193 | ``` 194 | 195 | 4. Perform ARM template update to install LAD 3.0. LAD captures new text lines as they are written to the file and writes them to table rows and/or any specified sinks (JsonBlob or EventHub). Add following section to template under the "extensionProfile" -> "extensions" section and deploy: 196 | 197 | ```json 198 | { 199 | "properties": { 200 | "publisher": "Microsoft.Azure.Diagnostics", 201 | "type": "LinuxDiagnostic", 202 | "typeHandlerVersion": "3.0", 203 | "autoUpgradeMinorVersion": true, 204 | "settings": { 205 | "StorageAccount": "", 206 | "fileLogs": [ 207 | { 208 | "file": "/var/log/sfreverseproxy/stdout.log", 209 | "table": "SFReverseProxyDebugLog", 210 | "sinks": "" 211 | }, 212 | { 213 | "file": "/var/log/sfreverseproxy/request.log", 214 | "table": "SFReverseProxyRequestLog", 215 | "sinks": "" 216 | } 217 | ], 218 | "ladCfg": "" 219 | }, 220 | "protectedSettings": { 221 | "storageAccountName": "", 222 | "storageAccountSasToken": "" 223 | } 224 | }, 225 | "name": "" 226 | } 227 | ``` 228 | 5. The logs will be visible under the configured table name. 229 | For more information about LAD, refer to [Linux Diagnostic Extension](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/diagnostic-extension) 230 | 231 | -------------------------------------------------------------------------------- /deploy/reverseproxy.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | Reverse_Proxy_Service: 4 | deploy: 5 | replicas: -1 6 | image: microsoft/service-fabric-reverse-proxy:0.30.0 7 | volumes: 8 | - /var/lib/sfcerts:/var/lib/sfcerts 9 | environment: 10 | # Secure cluster settings 11 | # - SF_ClientCertCommonName= 12 | # - SF_ClientCertIssuerThumbprints= 13 | # - SF_ClusterCertCommonNames= 14 | # - SF_ClusterCertIssuerThumbprints= 15 | 16 | # Request Handling settings 17 | - HttpRequestConnectTimeoutMs=5000 18 | - DefaultHttpRequestTimeoutMs=120000 19 | # - RemoveServiceResponseHeaders 20 | 21 | # Secure Reverse Proxy settings 22 | - UseHttps=false 23 | # - ReverseProxyCertThumbprint= 24 | 25 | # Secure Service settings 26 | # - ServiceCertificateHash= 27 | # - ServiceCertificateAlternateNames= 28 | 29 | ports: 30 | - "19081:19081" 31 | -------------------------------------------------------------------------------- /deploycontainer.cmd: -------------------------------------------------------------------------------- 1 | 2 | set TAG=0.20.0 3 | set BRANCH=%1 4 | set GIT_COMMIT=%2 5 | set DOCKER_USERNAME=%3 6 | set DOCKER_PASSWORD=%4 7 | 8 | if "%BRANCH%" == "master" set BRANCH=microsoft 9 | if "%GIT_COMMIT%" == "" set GIT_COMMIT=Unknown 10 | 11 | echo TAG=%TAG% 12 | echo BRANCH=%BRANCH% 13 | echo GIT_COMMIT=%GIT_COMMIT% 14 | 15 | set config=Release 16 | 17 | echo dotnet publish -c %config% sfintegration -r win10-x64 --self-contained 18 | dotnet publish -c %config% sfintegration -r win10-x64 --self-contained 19 | 20 | set publish_path=.\sfintegration\bin\%config%\netcoreapp2.1\win10-x64\publish 21 | 22 | REM # Build the Docker images 23 | set docker_cmd=docker build -t %BRANCH%/service-fabric-reverse-proxy:windows-%TAG% --label GIT_COMMIT=%GIT_COMMIT% -f %publish_path%\Dockerfile.windows %publish_path%\. 24 | echo %docker_cmd% 25 | %docker_cmd% 26 | echo docker tag %BRANCH%/service-fabric-reverse-proxy:windows-%TAG% %BRANCH%/service-fabric-reverse-proxy:windows-latest 27 | docker tag %BRANCH%/service-fabric-reverse-proxy:windows-%TAG% %BRANCH%/service-fabric-reverse-proxy:windows-latest 28 | 29 | REM # Login to Docker Hub and upload images 30 | REM echo $DOCKER_PASSWORD | docker login -u="$DOCKER_USERNAME" --password-stdin 31 | REM echo docker push %BRANCH%/service-fabric-reverse-proxy:windows-%TAG% 32 | REM docker push %BRANCH%/service-fabric-reverse-proxy:windows-%TAG% 33 | REM echo docker push %BRANCH%/service-fabric-reverse-proxy:windows-latest 34 | REM docker push %BRANCH%/service-fabric-reverse-proxy:windows-latest 35 | -------------------------------------------------------------------------------- /deploycontainer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TAG=0.31.0 4 | BRANCH=$1 5 | GIT_COMMIT=$2 6 | DOCKER_USERNAME=$3 7 | DOCKER_PASSWORD=$4 8 | 9 | if [ $BRANCH = "master" ]; then 10 | BRANCH=microsoft 11 | fi 12 | 13 | if [ -z $GIT_COMMIT ]; then 14 | GIT_COMMIT="Unknown" 15 | fi 16 | 17 | echo TAG=$TAG 18 | echo BRANCH=$BRANCH 19 | echo GIT_COMMIT=$GIT_COMMIT 20 | 21 | config=Release 22 | 23 | echo dotnet build -c $config sfintegration 24 | dotnet build -c $config sfintegration 25 | echo dotnet publish -c $config sfintegration -f netcoreapp2.1 -r ubuntu.16.04-x64 --self-contained 26 | dotnet publish -c $config sfintegration -f netcoreapp2.1 -r ubuntu.16.04-x64 --self-contained 27 | 28 | # Pull the previous image to speed up image generation 29 | docker pull $BRANCH/service-fabric-reverse-proxy:latest 30 | 31 | # Build the Docker images 32 | echo docker build -t $BRANCH/service-fabric-reverse-proxy:$TAG --label GIT_COMMIT=$GIT_COMMIT ./sfintegration/bin/$config/netcoreapp2.1/ubuntu.16.04-x64/publish/. 33 | docker build -t $BRANCH/service-fabric-reverse-proxy:$TAG --label GIT_COMMIT=$GIT_COMMIT ./sfintegration/bin/$config/netcoreapp2.1/ubuntu.16.04-x64/publish/. 34 | echo docker tag $BRANCH/service-fabric-reverse-proxy:$TAG $BRANCH/service-fabric-reverse-proxy:xenial-$TAG 35 | docker tag $BRANCH/service-fabric-reverse-proxy:$TAG $BRANCH/service-fabric-reverse-proxy:xenial-$TAG 36 | echo docker tag $BRANCH/service-fabric-reverse-proxy:$TAG $BRANCH/service-fabric-reverse-proxy:latest 37 | docker tag $BRANCH/service-fabric-reverse-proxy:$TAG $BRANCH/service-fabric-reverse-proxy:latest 38 | 39 | # Login to Docker Hub and upload images 40 | echo $DOCKER_PASSWORD | docker login -u="$DOCKER_USERNAME" --password-stdin 41 | echo docker push $BRANCH/service-fabric-reverse-proxy:$TAG 42 | docker push $BRANCH/service-fabric-reverse-proxy:$TAG 43 | echo docker push $BRANCH/service-fabric-reverse-proxy:xenial-$TAG 44 | docker push $BRANCH/service-fabric-reverse-proxy:xenial-$TAG 45 | echo docker push $BRANCH/service-fabric-reverse-proxy:latest 46 | docker push $BRANCH/service-fabric-reverse-proxy:latest 47 | -------------------------------------------------------------------------------- /gateway/Dockerfile.windows: -------------------------------------------------------------------------------- 1 | FROM microsoft/nanoserver:1803 2 | 3 | # copy application files and setup the start command 4 | WORKDIR c:\\app 5 | COPY . c:\\app 6 | CMD startup.cmd 7 | -------------------------------------------------------------------------------- /gateway/Dockerfile.windows.1709: -------------------------------------------------------------------------------- 1 | FROM microsoft/nanoserver:1709 2 | 3 | # copy application files and setup the start command 4 | WORKDIR c:\\app 5 | COPY . c:\\app 6 | CMD startup.cmd 7 | -------------------------------------------------------------------------------- /gateway/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.NetworkInformation; 4 | using System.Net.Sockets; 5 | using System.Threading.Tasks; 6 | 7 | namespace startup 8 | { 9 | class Program 10 | { 11 | public const string Env_Fabric_NodeIPOrFQDN = "Fabric_NodeIPOrFQDN"; 12 | public const string Env_GatewayMode = "GatewayMode"; 13 | public const string Env_Fabric_Endpoint_GatewayProxyResolverEndpoint = "Fabric_Endpoint_GatewayProxyResolverEndpoint"; 14 | public const string Env_Gateway_Resolver_Uses_Dynamic_Port = "Gateway_Resolver_Uses_Dynamic_Port"; 15 | 16 | public const string GatewayProxyResolverStaticPort = "19079"; 17 | public const string LocalProxyResolverURI = "tcp://127.0.0.1:5000"; 18 | 19 | public const string DiscoveryType_Static = "static"; 20 | public const string DiscoveryType_LogicalDns = "logical_dns"; 21 | public static readonly TimeSpan EndpointConnectTimeout = TimeSpan.FromSeconds(5); 22 | public const int ConnectRetryCount = 10; 23 | public static readonly TimeSpan DelayBetweenRetry = TimeSpan.FromSeconds(10); 24 | 25 | private static string fabricNodeIpOrFQDN; 26 | private static string gatewayMode; 27 | private static string proxyResolverEndpointPort; 28 | private static string isDynamicPortResolver; 29 | 30 | private static int indentLevel = 0; 31 | private static string indentSpaces = ""; 32 | 33 | public enum IndentOperation 34 | { 35 | NoChange, 36 | BeginLevel, 37 | EndLevel 38 | } 39 | 40 | public static void LogMessage(string message, IndentOperation indentOp = IndentOperation.NoChange) 41 | { 42 | if (indentOp == IndentOperation.EndLevel) 43 | { 44 | indentLevel--; 45 | if (indentLevel < 0) indentLevel = 0; 46 | indentSpaces = new string(' ', indentLevel * 2); 47 | } 48 | // Get the local time zone and the current local time and year. 49 | DateTime currentDate = DateTime.UtcNow; 50 | System.Console.WriteLine("[{0}][info][startup] {2}{1}", currentDate.ToString("yyyy-MM-dd HH:mm:ss.fffZ"), message, indentSpaces); 51 | if (indentOp == IndentOperation.BeginLevel) 52 | { 53 | indentLevel++; 54 | indentSpaces = new string(' ', indentLevel * 2); 55 | } 56 | } 57 | 58 | private static string GetHostEntry(string hostname) 59 | { 60 | try 61 | { 62 | LogMessage(string.Format("Trying to get HostEntry for {0}", hostname)); 63 | IPHostEntry host = Dns.GetHostEntry(hostname); 64 | 65 | LogMessage(string.Format("GetHostEntry({0}) returns:", hostname)); 66 | 67 | foreach (IPAddress address in host.AddressList) 68 | { 69 | LogMessage(string.Format(" {0}", address.ToString())); 70 | } 71 | return hostname; 72 | } 73 | catch (Exception e) 74 | { 75 | LogMessage(string.Format("Exception= {0}", e.ToString())); 76 | } 77 | return null; 78 | } 79 | 80 | private static string GetDNSResolveableHostName(string hostname) 81 | { 82 | foreach (NetworkInterface networkInterface in NetworkInterface.GetAllNetworkInterfaces()) 83 | { 84 | var dnsSuffix = networkInterface.GetIPProperties().DnsSuffix; 85 | if (string.IsNullOrWhiteSpace(dnsSuffix)) 86 | { 87 | continue; 88 | } 89 | 90 | if (hostname.EndsWith(dnsSuffix)) 91 | { 92 | // We have a hostname that matches one of the DNS suffix. 93 | // Don't try appending other suffixes and resolve 94 | break; 95 | } 96 | 97 | var newHostName = hostname + "." + dnsSuffix; 98 | if (GetHostEntry(newHostName) != null) 99 | { 100 | LogMessage(string.Format("Reachable hostname: {0}", newHostName)); 101 | return newHostName; 102 | } 103 | } 104 | 105 | LogMessage(string.Format("Did not find a reachable hostname for: {0}", hostname)); 106 | #if (DEVELOPMENT_ENVIRONMENT) 107 | return null; 108 | #else 109 | return hostname; 110 | #endif 111 | } 112 | 113 | static string GetReachableIPAddress() 114 | { 115 | var resolverPort = int.Parse(proxyResolverEndpointPort); 116 | for (int i = 0; i < ConnectRetryCount; i++) 117 | { 118 | foreach (NetworkInterface networkInterface in NetworkInterface.GetAllNetworkInterfaces()) 119 | { 120 | foreach (var address in networkInterface.GetIPProperties().GatewayAddresses) 121 | { 122 | if (IPAddress.IsLoopback(address.Address) || address.Address.AddressFamily != AddressFamily.InterNetwork) 123 | { 124 | continue; 125 | } 126 | 127 | LogMessage(string.Format("Trying to connect to : {0}:{1}", 128 | address.Address.ToString(), 129 | proxyResolverEndpointPort)); 130 | 131 | Socket s = new Socket( 132 | AddressFamily.InterNetwork, 133 | SocketType.Stream, 134 | ProtocolType.Tcp); 135 | try 136 | { 137 | IAsyncResult result = s.BeginConnect(address.Address.ToString(), resolverPort, null, null); 138 | result.AsyncWaitHandle.WaitOne(EndpointConnectTimeout, true); 139 | 140 | if (s.Connected) 141 | { 142 | LogMessage(string.Format("Sucessfully connected : {0}:{1}", 143 | address.Address.ToString(), 144 | proxyResolverEndpointPort)); 145 | 146 | s.Close(); 147 | s = null; 148 | 149 | return address.Address.ToString(); 150 | } 151 | } 152 | catch (Exception) 153 | { 154 | } 155 | } 156 | Task.Delay(DelayBetweenRetry); 157 | } 158 | } 159 | 160 | LogMessage(string.Format("Did not find a reachable IP for: {0}", fabricNodeIpOrFQDN)); 161 | return null; 162 | } 163 | 164 | static string GetDiscoveryType() 165 | { 166 | var hostNameType = Uri.CheckHostName(fabricNodeIpOrFQDN); 167 | switch (hostNameType) 168 | { 169 | case UriHostNameType.IPv4: 170 | case UriHostNameType.IPv6: 171 | return DiscoveryType_Static; 172 | 173 | case UriHostNameType.Dns: 174 | if (GetHostEntry(fabricNodeIpOrFQDN) == null) 175 | { 176 | var hostname = GetDNSResolveableHostName(fabricNodeIpOrFQDN); 177 | if (hostname != null) 178 | { 179 | fabricNodeIpOrFQDN = hostname; 180 | return DiscoveryType_LogicalDns; 181 | } 182 | } 183 | 184 | // Cannot use DNS 185 | // Let's try and check if we can reach the end point on one of the 186 | // network's Default Gateway IPs 187 | var ipAddress = GetReachableIPAddress(); 188 | if (ipAddress != null) 189 | { 190 | fabricNodeIpOrFQDN = ipAddress; 191 | return DiscoveryType_Static; 192 | } 193 | return DiscoveryType_LogicalDns; 194 | 195 | default: 196 | return String.Empty; 197 | } 198 | } 199 | 200 | static string GetResolverURI() 201 | { 202 | if (!Convert.ToBoolean(gatewayMode)) 203 | { 204 | LogMessage(string.Format("Proxy not running in GatewayMode. Use local resolver {0}", LocalProxyResolverURI)); 205 | return LocalProxyResolverURI; 206 | } 207 | 208 | if (!Convert.ToBoolean(isDynamicPortResolver)) 209 | { 210 | proxyResolverEndpointPort = GatewayProxyResolverStaticPort; 211 | } 212 | 213 | string resolverURI = String.Format("tcp://{0}:{1}", fabricNodeIpOrFQDN, proxyResolverEndpointPort); 214 | return resolverURI; 215 | } 216 | 217 | static void ReplaceContents(string inputFile, string outputFile) 218 | { 219 | var fileContents = System.IO.File.ReadAllText(inputFile); 220 | 221 | string discoveryType = GetDiscoveryType(); 222 | string resolverUri = GetResolverURI(); 223 | 224 | LogMessage(string.Format("discovery type: {0}, resolver URI: {1}", discoveryType, resolverUri)); 225 | fileContents = fileContents.Replace("DISCOVERYTYPE", discoveryType); 226 | fileContents = fileContents.Replace("RESOLVERURI", resolverUri); 227 | 228 | System.IO.File.WriteAllText(outputFile, fileContents); 229 | } 230 | 231 | static int Main(string[] args) 232 | { 233 | #if (DEVELOPMENT_ENVIRONMENT) 234 | LogMessage("Development environment"); 235 | #else 236 | LogMessage("Mesh environment"); 237 | #endif 238 | 239 | if (args.Length != 2) 240 | { 241 | LogMessage(string.Format("syntax: UpdateConfig.exe confile_template_file output_config_file")); 242 | return 0; 243 | } 244 | 245 | fabricNodeIpOrFQDN = Environment.GetEnvironmentVariable(Env_Fabric_NodeIPOrFQDN); 246 | gatewayMode = Environment.GetEnvironmentVariable(Env_GatewayMode); 247 | proxyResolverEndpointPort = Environment.GetEnvironmentVariable(Env_Fabric_Endpoint_GatewayProxyResolverEndpoint); 248 | isDynamicPortResolver = Environment.GetEnvironmentVariable(Env_Gateway_Resolver_Uses_Dynamic_Port); 249 | 250 | LogMessage(string.Format("Environment variable {0} = {1}", Env_Fabric_NodeIPOrFQDN, fabricNodeIpOrFQDN)); 251 | LogMessage(string.Format("Environment variable {0} = {1}", Env_Fabric_Endpoint_GatewayProxyResolverEndpoint, proxyResolverEndpointPort)); 252 | LogMessage(string.Format("Environment variable {0} = {1}", Env_GatewayMode, gatewayMode)); 253 | LogMessage(string.Format("Environment variable {0} = {1}", Env_Gateway_Resolver_Uses_Dynamic_Port, isDynamicPortResolver)); 254 | 255 | Program.ReplaceContents(args[0], args[1]); 256 | 257 | return 1; 258 | } 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /gateway/config.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "listeners": [ 3 | ], 4 | "lds": { 5 | "cluster": "lds", 6 | "refresh_delay_ms": 10000 7 | }, 8 | "admin": { 9 | "access_log_path": "./log/sfreverseproxy.access_log.admin.log", 10 | "address": "tcp://0.0.0.0:8001" 11 | }, 12 | "cluster_manager": { 13 | "sds": { 14 | "cluster": { 15 | "name": "sds", 16 | "type": "DISCOVERYTYPE", 17 | "connect_timeout_ms": 5000, 18 | "lb_type": "round_robin", 19 | "hosts": [ 20 | { 21 | "url": "RESOLVERURI" 22 | } 23 | ] 24 | }, 25 | "refresh_delay_ms": 10000 26 | }, 27 | "clusters": [ 28 | { 29 | "name": "lds", 30 | "type": "DISCOVERYTYPE", 31 | "connect_timeout_ms": 5000, 32 | "lb_type": "round_robin", 33 | "hosts": [ 34 | { 35 | "url": "RESOLVERURI" 36 | } 37 | ] 38 | }, 39 | { 40 | "name": "rds", 41 | "type": "DISCOVERYTYPE", 42 | "connect_timeout_ms": 5000, 43 | "lb_type": "round_robin", 44 | "hosts": [ 45 | { 46 | "url": "RESOLVERURI" 47 | } 48 | ] 49 | } 50 | ], 51 | "cds": { 52 | "cluster": { 53 | "name": "cds", 54 | "connect_timeout_ms": 5000, 55 | "lb_type": "round_robin", 56 | "type": "DISCOVERYTYPE", 57 | "hosts": [ 58 | { 59 | "url": "RESOLVERURI" 60 | } 61 | ] 62 | }, 63 | "refresh_delay_ms": 10000 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /gateway/deploycontainer.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | 4 | set TEST_CONTAINER=-test 5 | if "RELEASE_BUILD" == "TRUE" set TEST_CONTAINER= 6 | 7 | set TAG=0.30.0%TEST_CONTAINER% 8 | set BRANCH=%1 9 | set GIT_COMMIT=%2 10 | set DOCKER_USERNAME=%3 11 | set DOCKER_PASSWORD=%4 12 | 13 | if "%BRANCH%" == "master" set BRANCH=seabreeze 14 | if "%GIT_COMMIT%" == "" set GIT_COMMIT=Unknown 15 | 16 | echo TAG=%TAG% 17 | echo BRANCH=%BRANCH% 18 | echo GIT_COMMIT=%GIT_COMMIT% 19 | 20 | set config=Release 21 | set deploy_environment=Mesh 22 | 23 | call :do_build 24 | 25 | set deploy_environment=Mesh_Development 26 | call :do_build 27 | 28 | exit /b 0 29 | 30 | :do_build 31 | 32 | set publish_path=.\bin\%config%\netcoreapp2.0\win10-x64\publish 33 | if "%deploy_environment%" == "Mesh_Development" set publish_path=.\bin\%config%\%deploy_environment%\netcoreapp2.0\win10-x64\publish 34 | 35 | set deploy_environment=%deploy_environment% 36 | set PUBLISH_CMD=dotnet publish -c %config% . -r win10-x64 --self-contained -o %publish_path% 37 | echo %PUBLISH_CMD% 38 | %PUBLISH_CMD% 39 | 40 | xcopy %ENVOY_BINARIES_PATH%\* %publish_path% /y 41 | 42 | REM # Build the Docker images 43 | set image_tag_base=windows 44 | if "%deploy_environment%" == "Mesh_Development" set image_tag_base=windows-devenv 45 | set docker_cmd=docker build -t %BRANCH%/service-fabric-reverse-proxy:%image_tag_base%-%TAG% --label GIT_COMMIT=%GIT_COMMIT% -f %publish_path%\Dockerfile.windows %publish_path%\. 46 | echo %docker_cmd% 47 | %docker_cmd% 48 | echo docker tag %BRANCH%/service-fabric-reverse-proxy:%image_tag_base%-%TAG% %BRANCH%/service-fabric-reverse-proxy:%image_tag_base%-latest%TEST_CONTAINER% 49 | docker tag %BRANCH%/service-fabric-reverse-proxy:%image_tag_base%-%TAG% %BRANCH%/service-fabric-reverse-proxy:%image_tag_base%-latest%TEST_CONTAINER% 50 | 51 | echo docker push %BRANCH%/service-fabric-reverse-proxy:%image_tag_base%-%TAG% 52 | docker push %BRANCH%/service-fabric-reverse-proxy:%image_tag_base%-%TAG% 53 | echo docker push %BRANCH%/service-fabric-reverse-proxy:%image_tag_base%-latest%TEST_CONTAINER% 54 | docker push %BRANCH%/service-fabric-reverse-proxy:%image_tag_base%-latest%TEST_CONTAINER% 55 | 56 | set docker_cmd=docker build -t %BRANCH%/service-fabric-reverse-proxy:%image_tag_base%-1709-%TAG% --label GIT_COMMIT=%GIT_COMMIT% -f %publish_path%\Dockerfile.windows.1709 %publish_path%\. 57 | echo %docker_cmd% 58 | %docker_cmd% 59 | echo docker tag %BRANCH%/service-fabric-reverse-proxy:%image_tag_base%-1709-%TAG% %BRANCH%/service-fabric-reverse-proxy:%image_tag_base%-1709-latest%TEST_CONTAINER% 60 | docker tag %BRANCH%/service-fabric-reverse-proxy:%image_tag_base%-1709-%TAG% %BRANCH%/service-fabric-reverse-proxy:%image_tag_base%-1709-latest%TEST_CONTAINER% 61 | 62 | echo docker push %BRANCH%/service-fabric-reverse-proxy:%image_tag_base%-1709-%TAG% 63 | docker push %BRANCH%/service-fabric-reverse-proxy:%image_tag_base%-1709-%TAG% 64 | echo docker push %BRANCH%/service-fabric-reverse-proxy:%image_tag_base%-1709-latest%TEST_CONTAINER% 65 | docker push %BRANCH%/service-fabric-reverse-proxy:%image_tag_base%-1709-latest%TEST_CONTAINER% 66 | exit /b 0 67 | -------------------------------------------------------------------------------- /gateway/startup.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal EnableExtensions EnableDelayedExpansion 3 | 4 | if not exist .\log mkdir .\log 5 | set path=%PATH%;%FabricCodePath% 6 | 7 | echo Environment 8 | set 9 | echo. 10 | 11 | set count=0 12 | echo Getting Resolver Endpoints 13 | set Fabric_Endpoint_GatewayProxyResolverEndpoint=UNKNOWN 14 | :waitforendpointfile 15 | set /a count=%count%+1 16 | if "%count%" == "100" goto :errorResolve 17 | if not exist "%Fabric_Folder_Application%\Resolver.Endpoints.txt" ( 18 | ping -n 10 127.0.0.1 > nul 19 | goto :waitforendpointfile 20 | ) 21 | 22 | if exist "%Fabric_Folder_Application%\Resolver.Endpoints.txt" ( 23 | for /f "tokens=4 delims=;" %%i in (%Fabric_Folder_Application%\Resolver.Endpoints.txt) do set Fabric_Endpoint_GatewayProxyResolverEndpoint=%%i 24 | ) 25 | 26 | echo Run startup to generate enoy config 27 | startup.exe config.template.json config.gateway.json 28 | 29 | set ENVOYCMD=envoy.exe --max-obj-name-len 256 -c config.gateway.json --service-cluster gateway_proxy --service-node ingress_node -l info 30 | echo %ENVOYCMD% 31 | 32 | %ENVOYCMD% 33 | 34 | echo envoy exited. 35 | goto :end 36 | 37 | :errorResolve 38 | echo Could not find Resolver Endpoints 39 | 40 | :end 41 | -------------------------------------------------------------------------------- /gateway/startup.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.0 6 | true 7 | win10-x64 8 | 9 | 10 | 11 | $(DefineConstants);DEVELOPMENT_ENVIRONMENT 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /service-fabric-reverse-proxy.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sfintegration", "sfintegration\sfintegration.csproj", "{07A4FEA9-6F5E-4168-9FDA-4A9B9F48AE33}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "startup", "startup\startup.csproj", "{1B5F5322-9D6D-4ACB-8453-3B413C35372E}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x64 = Debug|x64 14 | Debug|x86 = Debug|x86 15 | Release|Any CPU = Release|Any CPU 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {07A4FEA9-6F5E-4168-9FDA-4A9B9F48AE33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {07A4FEA9-6F5E-4168-9FDA-4A9B9F48AE33}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {07A4FEA9-6F5E-4168-9FDA-4A9B9F48AE33}.Debug|x64.ActiveCfg = Debug|x64 26 | {07A4FEA9-6F5E-4168-9FDA-4A9B9F48AE33}.Debug|x64.Build.0 = Debug|x64 27 | {07A4FEA9-6F5E-4168-9FDA-4A9B9F48AE33}.Debug|x86.ActiveCfg = Debug|x86 28 | {07A4FEA9-6F5E-4168-9FDA-4A9B9F48AE33}.Debug|x86.Build.0 = Debug|x86 29 | {07A4FEA9-6F5E-4168-9FDA-4A9B9F48AE33}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {07A4FEA9-6F5E-4168-9FDA-4A9B9F48AE33}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {07A4FEA9-6F5E-4168-9FDA-4A9B9F48AE33}.Release|x64.ActiveCfg = Release|x64 32 | {07A4FEA9-6F5E-4168-9FDA-4A9B9F48AE33}.Release|x64.Build.0 = Release|x64 33 | {07A4FEA9-6F5E-4168-9FDA-4A9B9F48AE33}.Release|x86.ActiveCfg = Release|x86 34 | {07A4FEA9-6F5E-4168-9FDA-4A9B9F48AE33}.Release|x86.Build.0 = Release|x86 35 | {1B5F5322-9D6D-4ACB-8453-3B413C35372E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {1B5F5322-9D6D-4ACB-8453-3B413C35372E}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {1B5F5322-9D6D-4ACB-8453-3B413C35372E}.Debug|x64.ActiveCfg = Debug|x64 38 | {1B5F5322-9D6D-4ACB-8453-3B413C35372E}.Debug|x64.Build.0 = Debug|x64 39 | {1B5F5322-9D6D-4ACB-8453-3B413C35372E}.Debug|x86.ActiveCfg = Debug|x86 40 | {1B5F5322-9D6D-4ACB-8453-3B413C35372E}.Debug|x86.Build.0 = Debug|x86 41 | {1B5F5322-9D6D-4ACB-8453-3B413C35372E}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {1B5F5322-9D6D-4ACB-8453-3B413C35372E}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {1B5F5322-9D6D-4ACB-8453-3B413C35372E}.Release|x64.ActiveCfg = Release|x64 44 | {1B5F5322-9D6D-4ACB-8453-3B413C35372E}.Release|x64.Build.0 = Release|x64 45 | {1B5F5322-9D6D-4ACB-8453-3B413C35372E}.Release|x86.ActiveCfg = Release|x86 46 | {1B5F5322-9D6D-4ACB-8453-3B413C35372E}.Release|x86.Build.0 = Release|x86 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /sfintegration/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp2.0/webapi.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | "stopAtEntry": false, 17 | "internalConsoleOptions": "openOnSessionStart", 18 | "launchBrowser": { 19 | "enabled": true, 20 | "args": "${auto-detect-url}", 21 | "windows": { 22 | "command": "cmd.exe", 23 | "args": "/C start ${auto-detect-url}" 24 | }, 25 | "osx": { 26 | "command": "open" 27 | }, 28 | "linux": { 29 | "command": "xdg-open" 30 | } 31 | }, 32 | "env": { 33 | "ASPNETCORE_ENVIRONMENT": "Development", 34 | "LD_LIBRARY_PATH": "/opt/microsoft/servicefabric/bin/Fabric/Fabric.Code", 35 | "__STANDALONE_TESTING__": "true" 36 | }, 37 | "sourceFileMap": { 38 | "/Views": "${workspaceFolder}/Views" 39 | } 40 | }, 41 | { 42 | "name": ".NET Core Attach", 43 | "type": "coreclr", 44 | "request": "attach", 45 | "processId": "${command:pickProcess}" 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /sfintegration/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "taskName": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/webapi.csproj" 11 | ], 12 | "problemMatcher": "$msCompile" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /sfintegration/Controllers/v1.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using System; 8 | using System.Collections.Generic; 9 | using Microsoft.AspNetCore.Mvc; 10 | using Newtonsoft.Json.Linq; 11 | using sfintegration.envoymodels; 12 | 13 | namespace webapi.Controllers 14 | { 15 | [Produces("application/json")] 16 | [Route("[controller]")] 17 | public class v1 : Controller 18 | { 19 | [HttpGet] 20 | public IActionResult GetAsync() 21 | { 22 | return Ok( 23 | new { partitions = SF_Services.partitions_, services = SF_Services.services_ } 24 | ); 25 | } 26 | 27 | [HttpGet("envoydata")] 28 | public IActionResult GetList() 29 | { 30 | List ret = new List(); 31 | foreach (var pID in SF_Services.partitions_) 32 | { 33 | ret.AddRange(SF_Services.EnvoyInformationForPartition(pID.Key)); 34 | } 35 | return Ok( 36 | new { clusters = ret } 37 | ); 38 | } 39 | 40 | [HttpGet("listeners/{service_cluster}/{service_node}")] 41 | public IActionResult GetListeners(string service_cluster, string service_node) 42 | { 43 | List ret = new List(); 44 | if (EnvoyDefaults.gateway_map == null) 45 | { 46 | return Ok(new { listeners = ret }); 47 | } 48 | int index = 0; 49 | foreach (var entry in EnvoyDefaults.gateway_map) 50 | { 51 | EnvoyListener info = new EnvoyListener( 52 | "gateway_" + entry.Key, "tcp://" + EnvoyDefaults.gateway_listen_ip + ":" + entry.Key, "gateway_proxy", entry.Value); 53 | ret.Add(info); 54 | index++; 55 | } 56 | return Ok( 57 | new { listeners = ret } 58 | ); 59 | } 60 | 61 | [HttpGet("clusters/{service_cluster}/{service_node}")] 62 | public IActionResult GetClusters(string service_cluster, string service_node) 63 | { 64 | List ret = new List(); 65 | if (SF_Services.partitions_ == null) 66 | { 67 | return Ok(new { clusters = ret }); 68 | } 69 | if (EnvoyDefaults.gateway_map != null) 70 | { 71 | foreach (var service in SF_Services.services_) 72 | { 73 | if (!EnvoyDefaults.gateway_clusternames.Contains(service.Key)) 74 | { 75 | continue; 76 | } 77 | EnvoyCluster info = new EnvoyCluster(service.Key); 78 | ret.Add(info); 79 | } 80 | } 81 | else 82 | { 83 | foreach (var pID in SF_Services.partitions_) 84 | { 85 | var info = SF_Services.EnvoyInformationForPartition(pID.Key); 86 | foreach (var service in info) 87 | { 88 | ret.Add(service.cluster); 89 | } 90 | } 91 | foreach (var service in SF_Services.services_) 92 | { 93 | if (!service.Value.StatefulService) 94 | { 95 | continue; 96 | } 97 | EnvoyCluster info = new EnvoyCluster(service.Key); 98 | ret.Add(info); 99 | } 100 | } 101 | 102 | return Ok( 103 | new { clusters = ret } 104 | ); 105 | } 106 | 107 | // removes stateful header - PartitionKey and returns remaining headers 108 | // if it matches filters 109 | // not secondary and listername matches give name (if not null) 110 | Tuple> RemoveStatefulHeadersAndFilter(List headers, string listenerName = null) 111 | { 112 | for (var i = headers.Count - 1; i >= 0; i--) 113 | { 114 | var header = headers[i]; 115 | var headerName = (string)header.GetValue("name"); 116 | if (headerName == "SecondaryReplicaIndex") 117 | { 118 | return new Tuple>(false, null); 119 | } 120 | if (headerName == "ListenerName" && listenerName != null && (string)header.GetValue("value") != listenerName) 121 | { 122 | return new Tuple>(false, null); 123 | } 124 | if (headerName == "PartitionKey") 125 | { 126 | headers.RemoveAt(i); 127 | } 128 | } 129 | return new Tuple>(true, headers); 130 | } 131 | 132 | [HttpGet("routes/{name}/{service_cluster}/{service_node}")] 133 | public IActionResult GetRoutes(string name, string service_cluster, string service_node) 134 | { 135 | List virtual_hosts = new List(); 136 | List ret = new List(); 137 | if (SF_Services.partitions_ == null) 138 | { 139 | virtual_hosts.Add(new EnvoyVirtualHost( 140 | name, 141 | new List() { "*" }, 142 | ret)); 143 | return Ok(new 144 | { 145 | virtual_hosts 146 | }); 147 | } 148 | if (name.StartsWith("gateway_config|")) 149 | { 150 | return GetRoutesForGateway(name, virtual_hosts); 151 | } 152 | 153 | foreach (var pID in SF_Services.partitions_) 154 | { 155 | var info = SF_Services.EnvoyInformationForPartition(pID.Key); 156 | foreach (var service in info) 157 | { 158 | ret.AddRange(service.routes); 159 | } 160 | } 161 | foreach (var service in SF_Services.services_) 162 | { 163 | if (!service.Value.StatefulService) 164 | { 165 | continue; 166 | } 167 | string routeConfigForPartition = service.Value.Partitions[0].ToString() + "|" + service.Value.EndpointIndex.ToString() + "|0"; 168 | var info = SF_Services.EnvoyInformationForPartition(service.Value.Partitions[0]); 169 | foreach (var serviceInfo in info) 170 | { 171 | if (serviceInfo.cluster.name != routeConfigForPartition) 172 | { 173 | continue; 174 | } 175 | var routes = serviceInfo.routes; 176 | foreach (var route in routes) 177 | { 178 | route.cluster = service.Key; 179 | var tuple = RemoveStatefulHeadersAndFilter(route.headers); 180 | if (!tuple.Item1) 181 | { 182 | continue; 183 | } 184 | route.prefix_rewrite = "/"; 185 | ret.Add(route); 186 | } 187 | } 188 | } 189 | virtual_hosts.Add(new EnvoyVirtualHost( 190 | name, 191 | new List() { "*" }, 192 | ret)); 193 | return Ok( 194 | new 195 | { 196 | virtual_hosts, 197 | EnvoyDefaults.response_headers_to_remove 198 | }); 199 | } 200 | 201 | private IActionResult GetRoutesForGateway(string name, List ret) 202 | { 203 | var segments = name.Split("|"); 204 | if (segments.Length != 2 || 205 | EnvoyDefaults.gateway_map == null || 206 | !EnvoyDefaults.gateway_map.ContainsKey(segments[1])) 207 | { 208 | return Ok( 209 | new 210 | { 211 | virtual_hosts = new[] 212 | { 213 | new { 214 | name = "Dummy", 215 | domains = new List() { "*" }, 216 | routes = ret 217 | } 218 | } 219 | }); 220 | } 221 | 222 | 223 | var filterConfigs = EnvoyDefaults.gateway_map[segments[1]]; 224 | foreach (var config in filterConfigs) 225 | { 226 | if (config.Type == ListenerFilterConfig.ListenerFilterType.Http) 227 | { 228 | var httpHosts = ((ListenerHttpFilterConfig)config).Hosts; 229 | foreach (var host in httpHosts) 230 | { 231 | var routes = new List(); 232 | foreach (var route in host.Routes) 233 | { 234 | var serviceName = route.Destination.EnvoyServiceName(); 235 | if (!SF_Services.services_.ContainsKey(serviceName)) 236 | { 237 | continue; 238 | } 239 | var service = SF_Services.services_[serviceName]; 240 | foreach (var partition in service.Partitions) 241 | { 242 | string routeConfigForPartition = partition.ToString() + "|" + service.EndpointIndex.ToString() + "|0"; 243 | var pId = partition; 244 | var info = SF_Services.EnvoyInformationForPartition(pId); 245 | foreach (var serviceInfo in info) 246 | { 247 | if (serviceInfo.cluster.name != routeConfigForPartition) 248 | { 249 | continue; 250 | } 251 | foreach (var envoyRoute in serviceInfo.routes) 252 | { 253 | var tuple = RemoveStatefulHeadersAndFilter(envoyRoute.headers, route.Destination.EndpointName); 254 | if (!tuple.Item1) 255 | { 256 | continue; 257 | } 258 | 259 | // We should filter only on user specified headers 260 | envoyRoute.headers = new List(); 261 | envoyRoute.prefix = route.Match.Path.Value; 262 | envoyRoute.prefix_rewrite = route.Match.Path.Rewrite; 263 | if (route.Match.Headers != null) 264 | { 265 | foreach (var header in route.Match.Headers) 266 | { 267 | var envoyHeader = new JObject(); 268 | envoyHeader.Add("name", header.Name); 269 | envoyHeader.Add("value", header.Value); 270 | envoyRoute.headers.Add(envoyHeader); 271 | } 272 | } 273 | envoyRoute.cluster = serviceName; 274 | routes.Add(envoyRoute); 275 | } 276 | } 277 | } 278 | 279 | } 280 | var domain = new List() { host.Name }; 281 | var virtual_host = new EnvoyVirtualHost(host.Name, 282 | domain, 283 | routes); 284 | ret.Add(virtual_host); 285 | } 286 | } 287 | } 288 | 289 | return Ok( 290 | new 291 | { 292 | virtual_hosts = ret 293 | }); 294 | } 295 | 296 | [HttpGet("registration/{routeConfig}")] 297 | public IActionResult GetHosts(string routeConfig) 298 | { 299 | List ret = new List(); 300 | 301 | var nameSegements = routeConfig.Split('|'); 302 | // Deal with service name cluster as opposed to a partition cluster 303 | if (nameSegements[2] == "-2") 304 | { 305 | if (!SF_Services.services_.ContainsKey(routeConfig)) 306 | { 307 | return Ok(new { hosts = ret }); 308 | } 309 | var service = SF_Services.services_[routeConfig]; 310 | foreach (var partition in service.Partitions) 311 | { 312 | string routeConfigForPartition = partition.ToString() + "|" + service.EndpointIndex.ToString() + "|0"; 313 | var pId = partition; 314 | var info = SF_Services.EnvoyInformationForPartition(pId); 315 | foreach (var serviceInfo in info) 316 | { 317 | if (serviceInfo.cluster.name != routeConfigForPartition) 318 | { 319 | continue; 320 | } 321 | ret.AddRange(serviceInfo.hosts); 322 | } 323 | } 324 | } 325 | else 326 | { 327 | Guid pId = new Guid(nameSegements[0]); 328 | var info = SF_Services.EnvoyInformationForPartition(pId); 329 | foreach (var service in info) 330 | { 331 | if (service.cluster.name != routeConfig) 332 | { 333 | continue; 334 | } 335 | ret.AddRange(service.hosts); 336 | } 337 | } 338 | return Ok( 339 | new { hosts = ret } 340 | ); 341 | } 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /sfintegration/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM envoyproxy/envoy:866a4bab8c1e7809e26409ac9636843070b2f61c 2 | 3 | RUN apt-get update && \ 4 | DEBIAN_FRONTEND=noninteractive apt-get install -y \ 5 | ca-certificates \ 6 | software-properties-common \ 7 | apt-transport-https \ 8 | libssh2.1 \ 9 | liblttng-ust0 \ 10 | curl \ 11 | iproute2 \ 12 | net-tools 13 | 14 | EXPOSE 19081 15 | EXPOSE 8001 16 | WORKDIR /app 17 | 18 | COPY Third-Party-Notices.txt / 19 | COPY . /app 20 | RUN chmod 755 startup.sh 21 | RUN date > imagecreationtime.txt 22 | CMD ./startup.sh 23 | -------------------------------------------------------------------------------- /sfintegration/Dockerfile.windows: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:2.1.5-runtime-nanoserver-1803 2 | 3 | # copy application files and setup the start command 4 | WORKDIR c:\\app 5 | COPY . c:\\app 6 | CMD startup.cmd 7 | -------------------------------------------------------------------------------- /sfintegration/EnableResolveNotifications.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using System; 8 | using System.Fabric; 9 | using System.Fabric.Description; 10 | using Microsoft.ServiceFabric.Services.Client; 11 | using System.Collections.Generic; 12 | 13 | namespace ServiceFabric 14 | { 15 | namespace Helpers 16 | { 17 | /// 18 | /// Class to setup ResolveNotifications to update Fabric Client's Endpoint cache. 19 | /// 20 | public class EnableResolveNotifications 21 | { 22 | static private FabricClient client; 23 | 24 | static EnableResolveNotifications() 25 | { 26 | client = null; 27 | } 28 | 29 | static void InitClient(FabricClient cli, EventHandler handler) 30 | { 31 | if (client == null) 32 | { 33 | if (cli == null) 34 | { 35 | client = new FabricClient(); 36 | } 37 | else 38 | { 39 | client = cli; 40 | } 41 | if (handler != null) 42 | { 43 | client.ServiceManager.ServiceNotificationFilterMatched += handler; 44 | } 45 | 46 | ServicePartitionResolver resolver = 47 | new ServicePartitionResolver( 48 | delegate 49 | { 50 | return client; 51 | } 52 | ); 53 | ServicePartitionResolver.SetDefault(resolver); 54 | } 55 | } 56 | 57 | public static void RegisterNotificationFilter(string filter, FabricClient cli = null, EventHandler handler = null) 58 | { 59 | InitClient(cli, handler); 60 | client.ServiceManager.RegisterServiceNotificationFilterAsync( 61 | new ServiceNotificationFilterDescription( 62 | new Uri(filter), 63 | true, 64 | false 65 | ) 66 | ); 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /sfintegration/Program.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using Microsoft.AspNetCore; 8 | using Microsoft.AspNetCore.Hosting; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace webapi 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | BuildWebHost(args).Run(); 18 | } 19 | 20 | public static IWebHost BuildWebHost(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .ConfigureLogging((hostingContext, logging) => 23 | { 24 | logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); 25 | logging.SetMinimumLevel(LogLevel.Warning); 26 | }) 27 | .UseStartup() 28 | .Build(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sfintegration/ServiceData.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | using System.Fabric; 12 | using System.Fabric.Query; 13 | using Newtonsoft.Json.Linq; 14 | using ServiceFabric.Helpers; 15 | using Newtonsoft.Json; 16 | using System.Net.NetworkInformation; 17 | using System.Net; 18 | using System.Security.Cryptography.X509Certificates; 19 | using System.Text; 20 | using System.Runtime.InteropServices; 21 | using Gateway.Models; 22 | using sfintegration.envoymodels; 23 | 24 | namespace webapi 25 | { 26 | 27 | public class ListenerFilterConfig 28 | { 29 | public enum ListenerFilterType 30 | { 31 | Tcp, 32 | Http 33 | } 34 | 35 | public ListenerFilterConfig(string name, ListenerFilterType type, string clusterName) 36 | { 37 | Name = name; 38 | Type = type; 39 | ClusterName = clusterName; 40 | } 41 | 42 | public string Name { get; private set; } 43 | public ListenerFilterType Type { get; private set; } 44 | 45 | public string ClusterName { get; private set; } 46 | } 47 | 48 | public class ListenerHttpFilterConfig : ListenerFilterConfig 49 | { 50 | public ListenerHttpFilterConfig(string name, string clusterName, IList hosts) 51 | : base(name, ListenerFilterConfig.ListenerFilterType.Http, clusterName) 52 | { 53 | Hosts = hosts; 54 | } 55 | 56 | public IList Hosts { get; private set; } 57 | } 58 | 59 | public class EnvoyDefaults 60 | { 61 | private static int indentLevel = 0; 62 | private static string indentSpaces = ""; 63 | public enum IndentOperation 64 | { 65 | NoChange, 66 | BeginLevel, 67 | EndLevel 68 | } 69 | public static void LogMessage(string message, IndentOperation indentOp = IndentOperation.NoChange) 70 | { 71 | if (indentOp == IndentOperation.EndLevel) 72 | { 73 | indentLevel--; 74 | if (indentLevel < 0) indentLevel = 0; 75 | indentSpaces = new string(' ', indentLevel * 2); 76 | } 77 | // Get the local time zone and the current local time and year. 78 | DateTime currentDate = DateTime.UtcNow; 79 | System.Console.WriteLine("[{0}][info][sfintegration] {2}{1}", currentDate.ToString("yyyy-MM-dd HH:mm:ss.fffZ"), message, indentSpaces); 80 | if (indentOp == IndentOperation.BeginLevel) 81 | { 82 | indentLevel++; 83 | indentSpaces = new string(' ', indentLevel * 2); 84 | } 85 | } 86 | static EnvoyDefaults() 87 | { 88 | var connectTimeout = Environment.GetEnvironmentVariable("HttpRequestConnectTimeoutMs"); 89 | if (connectTimeout != null) 90 | { 91 | try 92 | { 93 | connect_timeout_ms = Convert.ToInt32(connectTimeout); 94 | } 95 | catch { } 96 | } 97 | 98 | var requestTimeout = Environment.GetEnvironmentVariable("DefaultHttpRequestTimeoutMs"); 99 | if (requestTimeout != null) 100 | { 101 | try 102 | { 103 | timeout_ms = Convert.ToInt32(requestTimeout); 104 | } 105 | catch { } 106 | } 107 | 108 | var removeResponseHeaders = Environment.GetEnvironmentVariable("RemoveServiceResponseHeaders"); 109 | if (removeResponseHeaders != null) 110 | { 111 | try 112 | { 113 | response_headers_to_remove.AddRange(removeResponseHeaders.Replace(" ", "").Split(',')); 114 | } 115 | catch { } 116 | } 117 | 118 | var useHttps = Environment.GetEnvironmentVariable("UseHttps"); 119 | if (useHttps != null && useHttps == "true") 120 | { 121 | var verify_certificate_hash = Environment.GetEnvironmentVariable("ServiceCertificateHash"); 122 | 123 | var subjectAlternateName = Environment.GetEnvironmentVariable("ServiceCertificateAlternateNames"); 124 | if (subjectAlternateName != null) 125 | { 126 | try 127 | { 128 | verify_subject_alt_name.AddRange(subjectAlternateName.Replace(" ", "").Split(',')); 129 | } 130 | catch { } 131 | } 132 | cluster_ssl_context = new EnvoyClusterSslContext(verify_certificate_hash, verify_subject_alt_name); 133 | } 134 | 135 | host_ip = Environment.GetEnvironmentVariable("Fabric_NodeIPOrFQDN"); 136 | if (host_ip == null || host_ip == "localhost") 137 | { 138 | bool runningInContainer = Environment.GetEnvironmentVariable("__STANDALONE_TESTING__") == null; 139 | if (runningInContainer) 140 | { 141 | // running in a container outside of Service Fabric or 142 | // running in a container on local dev cluster 143 | host_ip = GetInternalGatewayAddress(); 144 | } 145 | else if (Environment.GetEnvironmentVariable("__USE_LOCALHOST__") == null) 146 | { 147 | // running outside of Service Fabric or 148 | // running on local dev cluster 149 | host_ip = GetIpAddress(); 150 | } 151 | else 152 | { 153 | host_ip = "127.0.0.1"; 154 | } 155 | } 156 | var port = Environment.GetEnvironmentVariable("ManagementPort"); 157 | if (port != null) 158 | { 159 | management_port = port; 160 | } 161 | 162 | client_cert_subject_name = Environment.GetEnvironmentVariable("SF_ClientCertCommonName"); 163 | var issuer_thumbprints = Environment.GetEnvironmentVariable("SF_ClientCertIssuerThumbprints"); 164 | if (!string.IsNullOrWhiteSpace(issuer_thumbprints)) 165 | { 166 | client_cert_issuer_thumbprints = issuer_thumbprints.Split(','); 167 | } 168 | var server_common_names = Environment.GetEnvironmentVariable("SF_ClusterCertCommonNames"); 169 | if (!string.IsNullOrWhiteSpace(server_common_names)) 170 | { 171 | server_cert_common_names = server_common_names.Split(','); 172 | } 173 | var server_issuer_thumbprints = Environment.GetEnvironmentVariable("SF_ClusterCertIssuerThumbprints"); 174 | if (!string.IsNullOrWhiteSpace(server_issuer_thumbprints)) 175 | { 176 | server_cert_issuer_thumbprints = server_issuer_thumbprints.Split(','); 177 | } 178 | 179 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 180 | { 181 | fabricUri = new String[] { host_ip + ":" + management_port }; 182 | } 183 | else 184 | { 185 | fabricUri = null; 186 | } 187 | 188 | LogMessage(String.Format("Management Endpoint = {0}:{1}", host_ip, management_port)); 189 | if (fabricUri != null) 190 | { 191 | LogMessage(String.Format("Fabric Uri = {0}", fabricUri)); 192 | } 193 | else 194 | { 195 | LogMessage(String.Format("Fabric Uri = []")); 196 | } 197 | 198 | if (client_cert_subject_name != null) 199 | { 200 | LogMessage(String.Format("SF_ClientCertCommonName = {0}", client_cert_subject_name)); 201 | } 202 | if (issuer_thumbprints != null) 203 | { 204 | LogMessage(String.Format("SF_ClientCertIssuerThumbprints = {0}", issuer_thumbprints)); 205 | } 206 | if (server_cert_common_names != null) 207 | { 208 | LogMessage(String.Format("SF_ClusterCertCommonNames = {0}", server_cert_common_names)); 209 | } 210 | if (server_issuer_thumbprints != null) 211 | { 212 | LogMessage(String.Format("SF_ClusterCertIssuerThumbprints = {0}", server_issuer_thumbprints)); 213 | } 214 | 215 | var gateway_listen_network = ""; 216 | LogMessage("Getting Gateway_Config"); 217 | string gateway_config = Environment.GetEnvironmentVariable("Gateway_Config"); 218 | if (gateway_config != null && gateway_config != "") 219 | { 220 | LogMessage(String.Format("Gateway_Config = {0}", gateway_config)); 221 | var gatewayProperties = JsonConvert.DeserializeObject(gateway_config); 222 | 223 | LogMessage(String.Format("Gateway_Config: Deserialized")); 224 | gateway_listen_network = gatewayProperties.SourceNetwork.Name; 225 | 226 | gateway_map = new Dictionary>(); 227 | gateway_clusternames = new HashSet(); 228 | LogMessage(String.Format("Gateway_Config: Start processing")); 229 | if (gatewayProperties.Tcp != null) 230 | { 231 | foreach (var e in gatewayProperties.Tcp) 232 | { 233 | string serviceName = e.Destination.EnvoyServiceName(); 234 | if (!gateway_map.ContainsKey(e.Port.ToString()) || 235 | gateway_map[e.Port.ToString()] == null) 236 | { 237 | gateway_map[e.Port.ToString()] = new List(); 238 | } 239 | gateway_map[e.Port.ToString()].Add( 240 | new ListenerFilterConfig( 241 | e.Name, 242 | ListenerFilterConfig.ListenerFilterType.Tcp, 243 | serviceName)); 244 | gateway_clusternames.Add(serviceName); 245 | } 246 | } 247 | if (gatewayProperties.Http != null) 248 | { 249 | foreach (var httpConfig in gatewayProperties.Http) 250 | { 251 | string configName = "gateway_config|" + httpConfig.Port; 252 | if (!gateway_map.ContainsKey(httpConfig.Port.ToString()) || 253 | gateway_map[httpConfig.Port.ToString()] == null) 254 | { 255 | gateway_map[httpConfig.Port.ToString()] = new List(); 256 | } 257 | gateway_map[httpConfig.Port.ToString()].Add( 258 | new ListenerHttpFilterConfig( 259 | httpConfig.Name, 260 | configName, 261 | httpConfig.Hosts)); 262 | foreach (var host in httpConfig.Hosts) 263 | { 264 | foreach (var e in host.Routes) 265 | { 266 | var serviceName = e.Destination.EnvoyServiceName(); 267 | gateway_clusternames.Add(serviceName); 268 | } 269 | } 270 | } 271 | } 272 | LogMessage(String.Format("Gateway_Config: End processing")); 273 | } 274 | else 275 | LogMessage("Gateway_Config=null"); 276 | 277 | if (gateway_listen_network != null) 278 | { 279 | LogMessage(String.Format("Gateway_Listen_Network = {0}", gateway_listen_network)); 280 | gateway_listen_network = "[" + gateway_listen_network + "]"; 281 | var env = Environment.GetEnvironmentVariables(); 282 | var keys = env.Keys; 283 | foreach (System.Collections.DictionaryEntry de in env) 284 | { 285 | var variable = de.Key.ToString(); 286 | if (variable.Contains(gateway_listen_network) && variable.StartsWith("Fabric_NET")) 287 | { 288 | gateway_listen_ip = de.Value.ToString(); 289 | break; 290 | } 291 | } 292 | } 293 | else 294 | LogMessage("Gateway_Listen_Network=null"); 295 | 296 | LogMessage("gateway_listen_ip = " + gateway_listen_ip); 297 | } 298 | private static string GetInternalGatewayAddress() 299 | { 300 | foreach (NetworkInterface networkInterface in NetworkInterface.GetAllNetworkInterfaces()) 301 | { 302 | if (networkInterface.GetIPProperties().GatewayAddresses != null && 303 | networkInterface.GetIPProperties().GatewayAddresses.Count > 0) 304 | { 305 | foreach (GatewayIPAddressInformation gatewayAddr in networkInterface.GetIPProperties().GatewayAddresses) 306 | { 307 | if (gatewayAddr.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) 308 | { 309 | return gatewayAddr.Address.ToString(); 310 | } 311 | } 312 | } 313 | } 314 | throw new ArgumentNullException("internalgatewayaddress"); 315 | } 316 | 317 | private static string GetIpAddress() 318 | { 319 | var hostname = Dns.GetHostName(); 320 | IPHostEntry hostEntry = Dns.GetHostEntry(Dns.GetHostName()); 321 | IPAddress ipAddress = hostEntry.AddressList.FirstOrDefault( 322 | ip => 323 | (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)); 324 | if (ipAddress != null) 325 | { 326 | return ipAddress.ToString(); 327 | } 328 | throw new InvalidOperationException("HostIpAddress"); 329 | } 330 | 331 | public static string LocalHostFixup(string host) 332 | { 333 | if (host == null || host == "localhost") 334 | { 335 | return host_ip; 336 | } 337 | return host; 338 | } 339 | 340 | public static int connect_timeout_ms = 5000; 341 | 342 | public static int timeout_ms = 120000; 343 | 344 | public static List response_headers_to_remove = new List(); 345 | 346 | public static string verify_certificate_hash; 347 | 348 | public static List verify_subject_alt_name = new List(); 349 | 350 | public static EnvoyClusterSslContext cluster_ssl_context; 351 | 352 | public static string host_ip; 353 | 354 | public static string management_port = "19000"; 355 | 356 | public static string[] fabricUri; 357 | 358 | public static string client_cert_subject_name; 359 | 360 | public static string[] client_cert_issuer_thumbprints; 361 | 362 | public static string[] server_cert_common_names; 363 | 364 | public static string[] server_cert_issuer_thumbprints; 365 | 366 | public static bool is_gateway_config = false; 367 | 368 | public static string gateway_listen_ip = "0.0.0.0"; 369 | 370 | public static Dictionary> gateway_map; 371 | public static HashSet gateway_clusternames; 372 | } 373 | public class EnvoyClustersInformation 374 | { 375 | public EnvoyClustersInformation(string name, List routes, List hosts, bool isHttps = false) 376 | { 377 | cluster = new EnvoyCluster(name, EnvoyDefaults.connect_timeout_ms, isHttps, EnvoyDefaults.cluster_ssl_context); 378 | this.routes = routes; 379 | this.hosts = hosts; 380 | } 381 | 382 | [JsonProperty] 383 | public EnvoyCluster cluster; 384 | 385 | [JsonProperty] 386 | public List routes; 387 | 388 | [JsonProperty] 389 | public List hosts; 390 | } 391 | 392 | public class SF_EndpointInstance 393 | { 394 | public SF_EndpointInstance(ServiceEndpointRole role, Uri uri) 395 | { 396 | role_ = role; 397 | endpoint_ = uri; 398 | } 399 | 400 | [JsonProperty(PropertyName = "role")] 401 | public ServiceEndpointRole role_; 402 | 403 | [JsonProperty(PropertyName = "endpoint")] 404 | public Uri endpoint_; 405 | } 406 | 407 | public class SF_Endpoint 408 | { 409 | public SF_Endpoint(string name) 410 | { 411 | this.Name = name; 412 | } 413 | 414 | public void AddInstance(ServiceEndpointRole role, Uri uri) 415 | { 416 | if (instances == null) 417 | { 418 | instances = new List(); 419 | } 420 | instances.Add(new SF_EndpointInstance(role, uri)); 421 | instances.Sort((x, y) => 422 | { 423 | if (x.role_ - y.role_ != 0) 424 | return x.role_ - y.role_; 425 | return string.CompareOrdinal(x.endpoint_.AbsolutePath, y.endpoint_.AbsolutePath); 426 | }); 427 | } 428 | 429 | public int InstanceCount() 430 | { 431 | if (instances == null) 432 | { 433 | return 0; 434 | } 435 | return instances.Count; 436 | } 437 | 438 | public SF_EndpointInstance GetAt(int index) 439 | { 440 | return instances[index]; 441 | } 442 | 443 | [JsonProperty] 444 | public string Name; 445 | 446 | [JsonProperty] 447 | private List instances; 448 | } 449 | public class SF_Partition 450 | { 451 | public SF_Partition(Uri serviceName, ServiceKind serviceKind, ServicePartitionInformation partitionInformation, ServiceEndpointsVersion version, List listeners) 452 | { 453 | serviceName_ = serviceName; 454 | serviceKind_ = serviceKind; 455 | partitionInformation_ = partitionInformation; 456 | version_ = version; 457 | listeners_ = listeners; 458 | } 459 | 460 | [JsonProperty(PropertyName = "service_name")] 461 | public Uri serviceName_; 462 | 463 | [JsonProperty(PropertyName = "service_kind")] 464 | public ServiceKind serviceKind_; 465 | 466 | [JsonProperty(PropertyName = "partition_information")] 467 | public ServicePartitionInformation partitionInformation_; 468 | 469 | [JsonProperty(PropertyName = "version")] 470 | public ServiceEndpointsVersion version_; 471 | 472 | [JsonProperty(PropertyName = "listeners")] 473 | public List listeners_; 474 | } 475 | 476 | public class ServicePartitions 477 | { 478 | [JsonProperty] 479 | public int EndpointIndex; 480 | 481 | [JsonProperty] 482 | public bool StatefulService; 483 | 484 | [JsonProperty] 485 | public List Partitions; 486 | } 487 | 488 | public class SF_Services 489 | { 490 | public static Dictionary partitions_; 491 | 492 | public static Dictionary services_; 493 | 494 | // Temporary lists to handle initialization race 495 | static Dictionary partitionsAdd_ = new Dictionary(); 496 | 497 | static Dictionary partitionsRemove_ = new Dictionary(); 498 | 499 | static object lock_ = new object(); 500 | 501 | static private FabricClient client; 502 | static SF_Services() 503 | { 504 | try 505 | { 506 | partitions_ = null; 507 | 508 | if (!string.IsNullOrWhiteSpace(EnvoyDefaults.client_cert_subject_name) || 509 | EnvoyDefaults.client_cert_issuer_thumbprints != null) 510 | { 511 | EnvoyDefaults.LogMessage("Client: Creating, secure"); 512 | 513 | X509Credentials creds = new X509Credentials(); 514 | if (!string.IsNullOrWhiteSpace(EnvoyDefaults.client_cert_subject_name)) 515 | { 516 | creds.FindType = X509FindType.FindBySubjectName; 517 | creds.FindValue = EnvoyDefaults.client_cert_subject_name; 518 | if (EnvoyDefaults.client_cert_issuer_thumbprints != null) 519 | { 520 | foreach (var issuer in EnvoyDefaults.client_cert_issuer_thumbprints) 521 | { 522 | creds.IssuerThumbprints.Add(issuer); 523 | } 524 | } 525 | } 526 | else 527 | { 528 | creds.FindType = X509FindType.FindByThumbprint; 529 | creds.FindValue = EnvoyDefaults.client_cert_issuer_thumbprints[0]; 530 | } 531 | 532 | if (EnvoyDefaults.server_cert_common_names != null) 533 | { 534 | foreach (var commonName in EnvoyDefaults.server_cert_common_names) 535 | { 536 | creds.RemoteCommonNames.Add(commonName); 537 | } 538 | } 539 | else if (!string.IsNullOrWhiteSpace(EnvoyDefaults.client_cert_subject_name)) 540 | { 541 | creds.RemoteCommonNames.Add(EnvoyDefaults.client_cert_subject_name); 542 | } 543 | if (EnvoyDefaults.server_cert_issuer_thumbprints != null) 544 | { 545 | foreach (var issuer in EnvoyDefaults.server_cert_issuer_thumbprints) 546 | { 547 | creds.RemoteCertThumbprints.Add(issuer); 548 | } 549 | } 550 | else if (EnvoyDefaults.client_cert_issuer_thumbprints != null) 551 | { 552 | foreach (var issuer in EnvoyDefaults.client_cert_issuer_thumbprints) 553 | { 554 | creds.RemoteCertThumbprints.Add(issuer); 555 | } 556 | } 557 | creds.StoreLocation = StoreLocation.LocalMachine; 558 | creds.StoreName = "/var/lib/sfcerts"; 559 | 560 | EnvoyDefaults.LogMessage("Client: Start Create, secure"); 561 | client = new FabricClient(creds, EnvoyDefaults.fabricUri); 562 | EnvoyDefaults.LogMessage("Client: End Create, secure"); 563 | } 564 | else 565 | { 566 | EnvoyDefaults.LogMessage("Client: Creating, nonsecure"); 567 | client = new FabricClient(EnvoyDefaults.fabricUri); 568 | EnvoyDefaults.LogMessage("Client: End Create, nonsecure"); 569 | } 570 | EnvoyDefaults.LogMessage("Client sucessfully created"); 571 | 572 | EnableResolveNotifications.RegisterNotificationFilter("fabric:", client, Handler); 573 | EnvoyDefaults.LogMessage("Notification handler sucessfully set"); 574 | } 575 | catch (Exception e) 576 | { 577 | EnvoyDefaults.LogMessage(String.Format("Error = {0}", e)); 578 | } 579 | } 580 | 581 | private static string CalculateNameForService(SF_Partition partition, int listenerIndex) 582 | { 583 | StringBuilder serviceName = new StringBuilder(partition.serviceName_.PathAndQuery.Substring(1).Replace('/', '_')); 584 | if (partition.listeners_[listenerIndex].Name != "") 585 | { 586 | serviceName.Append("_"); 587 | serviceName.Append(partition.listeners_[listenerIndex].Name); 588 | } 589 | serviceName.Append("|*|-2"); 590 | 591 | return serviceName.ToString(); 592 | } 593 | 594 | // Assumes partitions_ is not null and the data structures are already locked 595 | private static void RemovePartitionFromService(Guid partitionId) 596 | { 597 | EnvoyDefaults.LogMessage(String.Format("Removed: {0}", partitionId)); 598 | foreach (var service in services_) 599 | { 600 | service.Value.Partitions.RemoveAll(x => x == partitionId); 601 | } 602 | foreach (var service in services_.Where(x => x.Value.Partitions.Count == 0).ToList()) 603 | { 604 | services_.Remove(service.Key); 605 | } 606 | } 607 | 608 | private static void AddPartitionToService(Guid partitionId, SF_Partition partitionInfo) 609 | { 610 | EnvoyDefaults.LogMessage(String.Format("Added: {0} = {1}", partitionId, 611 | JsonConvert.SerializeObject(partitionInfo))); 612 | for (int listenerIndex = 0; listenerIndex < partitionInfo.listeners_.Count; listenerIndex++) 613 | { 614 | string serviceName = CalculateNameForService(partitionInfo, listenerIndex); 615 | if (!services_.ContainsKey(serviceName)) 616 | { 617 | services_[serviceName] = new ServicePartitions 618 | { 619 | EndpointIndex = listenerIndex, 620 | StatefulService = (partitionInfo.serviceKind_ == ServiceKind.Stateful), 621 | Partitions = new List() 622 | }; 623 | } 624 | var entry = services_[serviceName].Partitions.Find(x => x == partitionId); 625 | if (entry == Guid.Empty) 626 | { 627 | services_[serviceName].Partitions.Add(partitionId); 628 | } 629 | } 630 | } 631 | 632 | private static void Handler(Object sender, EventArgs eargs) 633 | { 634 | EnvoyDefaults.LogMessage("Notification Handler: Start", EnvoyDefaults.IndentOperation.BeginLevel); 635 | try 636 | { 637 | var notification = ((FabricClient.ServiceManagementClient.ServiceNotificationEventArgs)eargs).Notification; 638 | if (notification.Endpoints.Count == 0) 639 | { 640 | //remove 641 | lock (lock_) 642 | { 643 | if (partitions_ != null) 644 | { 645 | partitions_.Remove(notification.PartitionId); 646 | RemovePartitionFromService(notification.PartitionId); 647 | } 648 | else 649 | { 650 | partitionsAdd_.Remove(notification.PartitionId); 651 | partitionsRemove_[notification.PartitionId] = null; 652 | } 653 | EnvoyDefaults.LogMessage("Notification Handler: End", EnvoyDefaults.IndentOperation.EndLevel); 654 | return; 655 | } 656 | } 657 | 658 | List listeners = new List(); 659 | ServiceEndpointRole role = ServiceEndpointRole.Invalid; 660 | EnvoyDefaults.LogMessage(String.Format("{0}: Start Enumerating Replicas", notification.PartitionId), 661 | EnvoyDefaults.IndentOperation.BeginLevel); 662 | EnvoyDefaults.LogMessage(String.Format("{0}: Replica Count = {1}", notification.PartitionId, 663 | notification.Endpoints.Count())); 664 | foreach (var notificationEndpoint in notification.Endpoints) 665 | { 666 | if (notificationEndpoint.Address.Length == 0) 667 | { 668 | EnvoyDefaults.LogMessage("_Node = "); 669 | continue; 670 | } 671 | 672 | EnvoyDefaults.LogMessage(String.Format("_Node = {0}", notificationEndpoint.Address)); 673 | JObject addresses; 674 | try 675 | { 676 | addresses = JObject.Parse(notificationEndpoint.Address); 677 | } 678 | catch 679 | { 680 | continue; 681 | } 682 | 683 | var notificationListeners = addresses["Endpoints"].Value(); 684 | foreach (var notificationListener in notificationListeners) 685 | { 686 | int listenerIndex = listeners.FindIndex(x => x.Name == notificationListener.Key); 687 | if (listenerIndex == -1) 688 | { 689 | listeners.Add(new SF_Endpoint(notificationListener.Key)); 690 | listenerIndex = listeners.Count - 1; 691 | } 692 | try 693 | { 694 | var listenerAddressString = notificationListener.Value.ToString(); 695 | EnvoyDefaults.LogMessage(String.Format("AddressString = {0}", listenerAddressString)); 696 | var listenerAddress = new UriBuilder(listenerAddressString).Uri; 697 | if (listenerAddress.HostNameType == UriHostNameType.Dns) 698 | { 699 | var ipaddrs = Dns.GetHostAddresses(listenerAddress.Host); 700 | foreach (var ipaddr in ipaddrs) 701 | { 702 | if (ipaddr.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) 703 | { 704 | var saddrstring = ipaddr.ToString(); 705 | if (saddrstring.StartsWith("172")) 706 | { 707 | listenerAddress = new Uri(listenerAddress.Scheme + "://" + 708 | saddrstring + 709 | ":" + listenerAddress.Port + listenerAddress.PathAndQuery); 710 | break; 711 | } 712 | } 713 | } 714 | } 715 | listeners[listenerIndex].AddInstance(notificationEndpoint.Role, listenerAddress); 716 | } 717 | catch (System.Exception e) 718 | { 719 | EnvoyDefaults.LogMessage(String.Format("Error = {0}", e)); 720 | } 721 | } 722 | if (role == ServiceEndpointRole.Invalid) 723 | { 724 | role = notificationEndpoint.Role; 725 | } 726 | } 727 | EnvoyDefaults.LogMessage(String.Format("{0}: End Enumerating Replicas", notification.PartitionId), 728 | EnvoyDefaults.IndentOperation.EndLevel); 729 | 730 | // Remove any listeners without active endpoints 731 | listeners.RemoveAll(x => x.InstanceCount() == 0); 732 | 733 | if (listeners.Count == 0) 734 | { 735 | EnvoyDefaults.LogMessage("Notification Handler: End", EnvoyDefaults.IndentOperation.EndLevel); 736 | return; 737 | } 738 | 739 | var partitionInfo = new SF_Partition(notification.ServiceName, 740 | (role == ServiceEndpointRole.Stateless) ? ServiceKind.Stateless : ServiceKind.Stateful, 741 | notification.PartitionInfo, 742 | notification.Version, 743 | listeners 744 | ); 745 | 746 | lock (lock_) 747 | { 748 | if (partitions_ != null) 749 | { 750 | partitions_[notification.PartitionId] = partitionInfo; 751 | AddPartitionToService(notification.PartitionId, partitionInfo); 752 | } 753 | else 754 | { 755 | partitionsRemove_.Remove(notification.PartitionId); 756 | partitionsAdd_[notification.PartitionId] = partitionInfo; 757 | } 758 | } 759 | } 760 | catch (Exception e) 761 | { 762 | EnvoyDefaults.LogMessage(String.Format("Error = {0}", e.Message)); 763 | EnvoyDefaults.LogMessage(String.Format("Error = {0}", e.StackTrace)); 764 | } 765 | EnvoyDefaults.LogMessage("Notification Handler: End", EnvoyDefaults.IndentOperation.EndLevel); 766 | } 767 | 768 | /// 769 | /// This function gathers the state of the cluster on startup and caches the information 770 | /// Changes to cluster state are handled through notifications. 771 | /// 772 | /// Capture information for each replica for every service running in the cluster. 773 | /// 774 | /// 775 | public static async Task InitializePartitionData() 776 | { 777 | EnvoyDefaults.LogMessage("InitializePartitionData started"); 778 | 779 | // Populate data locally 780 | Dictionary partitionData = new Dictionary(); 781 | 782 | var queryManager = client.QueryManager; 783 | 784 | ApplicationList applications = null; 785 | try 786 | { 787 | applications = await queryManager.GetApplicationListAsync(); 788 | } 789 | catch (Exception e) 790 | { 791 | EnvoyDefaults.LogMessage("GetApplicationListAsync: Failed"); 792 | EnvoyDefaults.LogMessage(String.Format("Error = {0}", e.Message)); 793 | EnvoyDefaults.LogMessage(String.Format("Error = {0}", e.StackTrace)); 794 | Environment.FailFast("GetApplicationListAsync failed"); 795 | } 796 | EnvoyDefaults.LogMessage("GetApplicationListAsync: Succeeded"); 797 | EnvoyDefaults.LogMessage(String.Format("GetApplicationListAsync: Application Count = {0}", applications.Count())); 798 | EnvoyDefaults.LogMessage("Start Enumerating Applications", EnvoyDefaults.IndentOperation.BeginLevel); 799 | foreach (var application in applications) 800 | { 801 | EnvoyDefaults.LogMessage(String.Format("{0}: Start Enumerating Services", application.ApplicationName), 802 | EnvoyDefaults.IndentOperation.BeginLevel); 803 | var services = await queryManager.GetServiceListAsync(application.ApplicationName); 804 | EnvoyDefaults.LogMessage(String.Format("{0}: Service Count = {1}", application.ApplicationName, services.Count())); 805 | foreach (var service in services) 806 | { 807 | EnvoyDefaults.LogMessage(String.Format("{0}: Start Enumerating Partitions", service.ServiceName), 808 | EnvoyDefaults.IndentOperation.BeginLevel); 809 | var partitions = await queryManager.GetPartitionListAsync(service.ServiceName); 810 | EnvoyDefaults.LogMessage(String.Format("{0}: Partition Count = {1}", service.ServiceName, partitions.Count())); 811 | foreach (var partition in partitions) 812 | { 813 | List listeners = new List(); 814 | 815 | EnvoyDefaults.LogMessage(String.Format("{0}: Start Enumerating Replicas", partition.PartitionInformation.Id), 816 | EnvoyDefaults.IndentOperation.BeginLevel); 817 | var replicas = await queryManager.GetReplicaListAsync(partition.PartitionInformation.Id); 818 | EnvoyDefaults.LogMessage(String.Format("{0}: Replica Count = {1}", partition.PartitionInformation.Id, replicas.Count())); 819 | foreach (var replica in replicas) 820 | { 821 | if (replica.ReplicaAddress.Length == 0) 822 | { 823 | EnvoyDefaults.LogMessage(String.Format("{0} = ", replica.NodeName)); 824 | continue; 825 | } 826 | 827 | EnvoyDefaults.LogMessage(String.Format("{0} = {1}", replica.NodeName, replica.ReplicaAddress)); 828 | JObject addresses; 829 | try 830 | { 831 | addresses = JObject.Parse(replica.ReplicaAddress); 832 | } 833 | catch 834 | { 835 | continue; 836 | } 837 | 838 | var replicaListeners = addresses["Endpoints"].Value(); 839 | foreach (var replicaListener in replicaListeners) 840 | { 841 | var role = ServiceEndpointRole.Stateless; 842 | if (partition.ServiceKind == ServiceKind.Stateful) 843 | { 844 | var statefulRole = ((StatefulServiceReplica)replica).ReplicaRole; 845 | switch (statefulRole) 846 | { 847 | case ReplicaRole.Primary: 848 | role = ServiceEndpointRole.StatefulPrimary; 849 | break; 850 | case ReplicaRole.ActiveSecondary: 851 | role = ServiceEndpointRole.StatefulSecondary; 852 | break; 853 | default: 854 | role = ServiceEndpointRole.Invalid; 855 | break; 856 | } 857 | } 858 | int listenerIndex = listeners.FindIndex(x => x.Name == replicaListener.Key); 859 | if (listenerIndex == -1) 860 | { 861 | listeners.Add(new SF_Endpoint(replicaListener.Key)); 862 | listenerIndex = listeners.Count - 1; 863 | } 864 | try 865 | { 866 | var listenerAddressString = replicaListener.Value.ToString(); 867 | EnvoyDefaults.LogMessage(String.Format("AddressString = {0}", listenerAddressString)); 868 | var listenerAddress = new UriBuilder(listenerAddressString).Uri; 869 | if (listenerAddress.HostNameType == UriHostNameType.Dns) 870 | { 871 | var ipaddrs = Dns.GetHostAddresses(listenerAddress.Host); 872 | foreach (var ipaddr in ipaddrs) 873 | { 874 | if (ipaddr.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) 875 | { 876 | var saddrstring = ipaddr.ToString(); 877 | if (saddrstring.StartsWith("172")) 878 | { 879 | listenerAddress = new Uri(listenerAddress.Scheme + "://" + 880 | saddrstring + 881 | ":" + listenerAddress.Port + listenerAddress.PathAndQuery); 882 | break; 883 | } 884 | } 885 | } 886 | } 887 | listeners[listenerIndex].AddInstance(role, listenerAddress); 888 | } 889 | catch (System.Exception e) 890 | { 891 | EnvoyDefaults.LogMessage(String.Format("Error = {0}", e)); 892 | } 893 | } 894 | EnvoyDefaults.LogMessage(String.Format("{0}: End Enumerating Replicas", partition.PartitionInformation.Id), 895 | EnvoyDefaults.IndentOperation.EndLevel); 896 | } 897 | 898 | // Remove any listeners without active endpoints 899 | listeners.RemoveAll(x => x.InstanceCount() == 0); 900 | 901 | if (listeners.Count != 0) 902 | { 903 | var partitionInfo = new SF_Partition(service.ServiceName, 904 | service.ServiceKind, 905 | partition.PartitionInformation, 906 | null, 907 | listeners); 908 | 909 | partitionData[partition.PartitionInformation.Id] = partitionInfo; 910 | } 911 | EnvoyDefaults.LogMessage(String.Format("{0}: End Enumerating Partitions", service.ServiceName), 912 | EnvoyDefaults.IndentOperation.EndLevel); 913 | } 914 | EnvoyDefaults.LogMessage(String.Format("{0}: End Enumerating Services", application.ApplicationName), 915 | EnvoyDefaults.IndentOperation.EndLevel); 916 | } 917 | EnvoyDefaults.LogMessage("End Enumerating Applications", 918 | EnvoyDefaults.IndentOperation.EndLevel); 919 | } 920 | 921 | // Process changes received through notifications 922 | lock (lock_) 923 | { 924 | foreach (var partition in partitionsAdd_) 925 | { 926 | partitionData[partition.Key] = partition.Value; 927 | } 928 | foreach (var partition in partitionsRemove_) 929 | { 930 | partitionData.Remove(partition.Key); 931 | } 932 | 933 | // Finally update global state, populate Service List and log details 934 | partitions_ = partitionData; 935 | services_ = new Dictionary(); 936 | foreach (var partition in partitionData) 937 | { 938 | AddPartitionToService(partition.Key, partition.Value); 939 | EnvoyDefaults.LogMessage(String.Format("Added: {0} = {1}", partition.Key, 940 | JsonConvert.SerializeObject(partition.Value))); 941 | } 942 | 943 | partitionsRemove_ = null; 944 | partitionsAdd_ = null; 945 | } 946 | } 947 | 948 | public static List EnvoyInformationForPartition(Guid partitionId) 949 | { 950 | lock (lock_) 951 | { 952 | List ret = new List(); 953 | if (partitions_ == null) 954 | { 955 | return ret; 956 | } 957 | 958 | SF_Partition partition; 959 | if (!partitions_.TryGetValue(partitionId, out partition)) 960 | { 961 | return ret; 962 | } 963 | 964 | //List ret = new List(); 965 | // stateless - partitionId | endpoint Index | -1 966 | // stateful - partitionId | endpoint Index | replica Index 967 | string prefix = partition.serviceName_.AbsolutePath; 968 | if (!prefix.EndsWith("/")) 969 | { 970 | prefix += "/"; 971 | } 972 | for (int endpointIndex = 0; endpointIndex < partition.listeners_.Count; endpointIndex++) 973 | { 974 | var ep = partition.listeners_[endpointIndex]; 975 | List replicaIndexes; 976 | bool statefulPartition = false; 977 | if (partition.serviceKind_ == ServiceKind.Stateless) 978 | { 979 | // For stateless, path is same for all replicas so we need to iterate just once 980 | replicaIndexes = new List() { 0 }; 981 | } 982 | else 983 | { 984 | replicaIndexes = new List(); 985 | replicaIndexes.AddRange(Enumerable.Range(0, ep.InstanceCount())); 986 | statefulPartition = true; 987 | } 988 | JObject endpointHeader = null; 989 | if (ep.Name != "") 990 | { 991 | endpointHeader = new JObject(); 992 | endpointHeader.Add("name", "ListenerName"); 993 | endpointHeader.Add("value", ep.Name); 994 | } 995 | 996 | for (int index = 0; index < replicaIndexes.Count; index++) 997 | { 998 | List routeData = new List(); 999 | List hostData = new List(); 1000 | string cluster = partitionId.ToString() + "|" + endpointIndex.ToString() + "|" + replicaIndexes[index].ToString(); 1001 | string prefix_rewrite = ep.GetAt(index).endpoint_.AbsolutePath; 1002 | List headers = new List(); 1003 | if (endpointHeader != null) 1004 | { 1005 | headers.Add(endpointHeader); 1006 | } 1007 | if (ep.GetAt(index).role_ == ServiceEndpointRole.StatefulSecondary) 1008 | { 1009 | JObject statefulSecondaryIndex = new JObject(); 1010 | statefulSecondaryIndex.Add("name", "SecondaryReplicaIndex"); 1011 | statefulSecondaryIndex.Add("value", replicaIndexes[index].ToString()); 1012 | headers.Add(statefulSecondaryIndex); 1013 | } 1014 | if (statefulPartition) 1015 | { 1016 | hostData.Add(new EnvoyHost(ep.GetAt(index).endpoint_.Host, ep.GetAt(index).endpoint_.Port)); 1017 | var partitionKind = partition.partitionInformation_.Kind; 1018 | if (partitionKind == ServicePartitionKind.Int64Range) 1019 | { 1020 | //if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 1021 | //{ 1022 | // // Remove once Windows has Envoy 1.6 1023 | // continue; 1024 | //} 1025 | 1026 | var rangePartitionInformation = (Int64RangePartitionInformation)partition.partitionInformation_; 1027 | if (rangePartitionInformation.LowKey == Int64.MinValue && 1028 | rangePartitionInformation.HighKey == Int64.MaxValue) 1029 | { 1030 | routeData.Add(new EnvoyRoute(cluster, prefix, prefix_rewrite, headers, EnvoyDefaults.timeout_ms)); 1031 | } 1032 | else 1033 | { 1034 | // Envoy - start and end of the range using half-open interval semantics [start, end) 1035 | var rangeEnd = rangePartitionInformation.HighKey; 1036 | if (rangePartitionInformation.HighKey == Int64.MaxValue) 1037 | { 1038 | JObject maxValHeader = new JObject(); 1039 | maxValHeader.Add("name", "PartitionKey"); 1040 | maxValHeader.Add("value", Int64.MaxValue.ToString()); 1041 | 1042 | List maxValHeaders = new List(headers); 1043 | maxValHeaders.Add(maxValHeader); 1044 | routeData.Add(new EnvoyRoute(cluster, prefix, prefix_rewrite, maxValHeaders, EnvoyDefaults.timeout_ms)); 1045 | } 1046 | else 1047 | { 1048 | rangeEnd++; 1049 | } 1050 | JObject partitionKeyHeader = new JObject(); 1051 | partitionKeyHeader.Add("name", "PartitionKey"); 1052 | 1053 | JObject range_match = new JObject(); 1054 | range_match.Add("start", rangePartitionInformation.LowKey); 1055 | range_match.Add("end", rangeEnd); 1056 | partitionKeyHeader.Add("range_match", range_match); 1057 | 1058 | List keyHeaders = new List(headers); 1059 | keyHeaders.Add(partitionKeyHeader); 1060 | routeData.Add(new EnvoyRoute(cluster, prefix, prefix_rewrite, keyHeaders, EnvoyDefaults.timeout_ms)); 1061 | } 1062 | } 1063 | else if (partitionKind == ServicePartitionKind.Named) 1064 | { 1065 | var namedPartitionInformation = (NamedPartitionInformation)partition.partitionInformation_; 1066 | 1067 | JObject partitionKeyHeader = new JObject(); 1068 | partitionKeyHeader.Add("name", "PartitionKey"); 1069 | partitionKeyHeader.Add("value", namedPartitionInformation.Name); 1070 | 1071 | List keyHeaders = new List(headers); 1072 | keyHeaders.Add(partitionKeyHeader); 1073 | routeData.Add(new EnvoyRoute(cluster, prefix, prefix_rewrite, keyHeaders, EnvoyDefaults.timeout_ms)); 1074 | } 1075 | } 1076 | else 1077 | { 1078 | // For stateless, capture addresses for all listeners for the one route 1079 | for (int i = 0; i < ep.InstanceCount(); i++) 1080 | { 1081 | var address = ep.GetAt(i); 1082 | hostData.Add(new EnvoyHost(address.endpoint_.Host, address.endpoint_.Port)); 1083 | } 1084 | routeData.Add(new EnvoyRoute(cluster, prefix, prefix_rewrite, headers, EnvoyDefaults.timeout_ms)); 1085 | } 1086 | if (ep.GetAt(index).endpoint_.Scheme == "https") 1087 | { 1088 | if (EnvoyDefaults.cluster_ssl_context != null) 1089 | { 1090 | ret.Add(new EnvoyClustersInformation(cluster, routeData, hostData, true)); 1091 | } 1092 | else 1093 | { 1094 | // Log information indicating that this end point is skipped 1095 | } 1096 | } 1097 | else 1098 | { 1099 | ret.Add(new EnvoyClustersInformation(cluster, routeData, hostData, false)); 1100 | } 1101 | } 1102 | } 1103 | return ret; 1104 | } 1105 | } 1106 | } 1107 | } 1108 | -------------------------------------------------------------------------------- /sfintegration/Startup.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using Microsoft.AspNetCore.Builder; 8 | using Microsoft.AspNetCore.Hosting; 9 | using Microsoft.Extensions.Configuration; 10 | using Microsoft.Extensions.DependencyInjection; 11 | 12 | namespace webapi 13 | { 14 | public class Startup 15 | { 16 | public Startup(IConfiguration configuration) 17 | { 18 | Configuration = configuration; 19 | } 20 | 21 | public IConfiguration Configuration { get; } 22 | 23 | // This method gets called by the runtime. Use this method to add services to the container. 24 | public void ConfigureServices(IServiceCollection services) 25 | { 26 | var t = SF_Services.InitializePartitionData(); 27 | t.Wait(); 28 | 29 | services.AddMvc(); 30 | } 31 | 32 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 33 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 34 | { 35 | if (env.IsDevelopment()) 36 | { 37 | app.UseDeveloperExceptionPage(); 38 | } 39 | 40 | app.UseMvc(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /sfintegration/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sfintegration/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": true, 4 | "Debug": { 5 | "LogLevel": { 6 | "Default": "Warning" 7 | } 8 | }, 9 | "Console": { 10 | "LogLevel": { 11 | "Microsoft.AspNetCore": "Error", 12 | "Default": "Information" 13 | } 14 | }, 15 | "LogLevel": { 16 | "Default": "Debug" 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /sfintegration/config.gateway.json: -------------------------------------------------------------------------------- 1 | { 2 | "listeners": [ 3 | ], 4 | "lds": { 5 | "cluster": "lds", 6 | "refresh_delay_ms": 10000 7 | }, 8 | "admin": { 9 | "access_log_path": "./log/sfreverseproxy.access_log.admin.log", 10 | "address": "tcp://0.0.0.0:8001" 11 | }, 12 | "cluster_manager": { 13 | "sds": { 14 | "cluster": { 15 | "name": "sds", 16 | "type": "static", 17 | "connect_timeout_ms": 5000, 18 | "lb_type": "round_robin", 19 | "hosts": [ 20 | { 21 | "url": "tcp://127.0.0.1:5000" 22 | } 23 | ] 24 | }, 25 | "refresh_delay_ms": 10000 26 | }, 27 | "clusters": [ 28 | { 29 | "name": "lds", 30 | "type": "static", 31 | "connect_timeout_ms": 5000, 32 | "lb_type": "round_robin", 33 | "hosts": [ 34 | { 35 | "url": "tcp://127.0.0.1:5000" 36 | } 37 | ] 38 | }, 39 | { 40 | "name": "rds", 41 | "type": "static", 42 | "connect_timeout_ms": 5000, 43 | "lb_type": "round_robin", 44 | "hosts": [ 45 | { 46 | "url": "tcp://127.0.0.1:5000" 47 | } 48 | ] 49 | } 50 | ], 51 | "cds": { 52 | "cluster": { 53 | "name": "cds", 54 | "connect_timeout_ms": 5000, 55 | "lb_type": "round_robin", 56 | "type": "static", 57 | "hosts": [ 58 | { 59 | "url": "tcp://127.0.0.1:5000" 60 | } 61 | ] 62 | }, 63 | "refresh_delay_ms": 10000 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /sfintegration/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "listeners": [ 3 | { 4 | "address": "tcp://0.0.0.0:19081", 5 | "filters": [ 6 | { 7 | "name": "http_connection_manager", 8 | "config": { 9 | "access_log": [ 10 | { 11 | "path": "./log/sfreverseproxy.access_log.log" 12 | } 13 | ], 14 | "codec_type": "auto", 15 | "stat_prefix": "reverse_proxy_http", 16 | "rds": { 17 | "cluster": "rds", 18 | "route_config_name": "reverse_proxy", 19 | "refresh_delay_ms": 10000 20 | }, 21 | "filters": [ 22 | { 23 | "type": "decoder", 24 | "name": "router", 25 | "config": {} 26 | } 27 | ] 28 | } 29 | } 30 | ] 31 | } 32 | ], 33 | "admin": { 34 | "access_log_path": "./log/sfreverseproxy.access_log.admin.log", 35 | "address": "tcp://0.0.0.0:8001" 36 | }, 37 | "cluster_manager": { 38 | "sds": { 39 | "cluster": { 40 | "name": "sds", 41 | "type": "static", 42 | "connect_timeout_ms": 5000, 43 | "lb_type": "round_robin", 44 | "hosts": [ 45 | { 46 | "url": "tcp://127.0.0.1:5000" 47 | } 48 | ] 49 | }, 50 | "refresh_delay_ms": 10000 51 | }, 52 | "clusters": [ 53 | { 54 | "name": "rds", 55 | "type": "static", 56 | "connect_timeout_ms": 5000, 57 | "lb_type": "round_robin", 58 | "hosts": [ 59 | { 60 | "url": "tcp://127.0.0.1:5000" 61 | } 62 | ] 63 | } 64 | ], 65 | "cds": { 66 | "cluster": { 67 | "name": "cds", 68 | "connect_timeout_ms": 5000, 69 | "lb_type": "round_robin", 70 | "type": "static", 71 | "hosts": [ 72 | { 73 | "url": "tcp://127.0.0.1:5000" 74 | } 75 | ] 76 | }, 77 | "refresh_delay_ms": 10000 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /sfintegration/config.secure.json: -------------------------------------------------------------------------------- 1 | { 2 | "listeners": [ 3 | { 4 | "name": "SecureReverseProxyListener", 5 | "address": "tcp://0.0.0.0:19081", 6 | "filters": [ 7 | { 8 | "name": "http_connection_manager", 9 | "config": { 10 | "access_log": [ 11 | { 12 | "path": "./log/sfreverseproxy.access_log.log" 13 | } 14 | ], 15 | "codec_type": "auto", 16 | "stat_prefix": "reverse_proxy_http", 17 | "rds": { 18 | "cluster": "rds", 19 | "route_config_name": "reverse_proxy", 20 | "refresh_delay_ms": 10000 21 | }, 22 | "filters": [ 23 | { 24 | "type": "decoder", 25 | "name": "router", 26 | "config": {} 27 | } 28 | ] 29 | } 30 | } 31 | ], 32 | "ssl_context": { 33 | "cert_chain_file": "/app/sfcerts/ReverseProxyCertThumbprint.crt", 34 | "private_key_file": "/app/sfcerts/ReverseProxyCertThumbprint.prv" 35 | } 36 | } 37 | ], 38 | "admin": { 39 | "access_log_path": "./log/sfreverseproxy.access_log.admin.log", 40 | "address": "tcp://0.0.0.0:8001" 41 | }, 42 | "cluster_manager": { 43 | "sds": { 44 | "cluster": { 45 | "name": "sds", 46 | "type": "static", 47 | "connect_timeout_ms": 5000, 48 | "lb_type": "round_robin", 49 | "hosts": [ 50 | { 51 | "url": "tcp://127.0.0.1:5000" 52 | } 53 | ] 54 | }, 55 | "refresh_delay_ms": 10000 56 | }, 57 | "clusters": [ 58 | { 59 | "name": "rds", 60 | "type": "static", 61 | "connect_timeout_ms": 5000, 62 | "lb_type": "round_robin", 63 | "hosts": [ 64 | { 65 | "url": "tcp://127.0.0.1:5000" 66 | } 67 | ] 68 | } 69 | ], 70 | "cds": { 71 | "cluster": { 72 | "name": "cds", 73 | "connect_timeout_ms": 5000, 74 | "lb_type": "round_robin", 75 | "type": "static", 76 | "hosts": [ 77 | { 78 | "url": "tcp://127.0.0.1:5000" 79 | } 80 | ] 81 | }, 82 | "refresh_delay_ms": 10000 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /sfintegration/config.windows.json: -------------------------------------------------------------------------------- 1 | { 2 | "listeners": [ 3 | { 4 | "address": "tcp://0.0.0.0:19081", 5 | "filters": [ 6 | { 7 | "name": "http_connection_manager", 8 | "config": { 9 | "access_log": [ 10 | { 11 | "path": "./log/sfreverseproxy.access_log.log" 12 | } 13 | ], 14 | "codec_type": "auto", 15 | "stat_prefix": "reverse_proxy_http", 16 | "rds": { 17 | "cluster": "rds", 18 | "route_config_name": "reverse_proxy", 19 | "refresh_delay_ms": 10000 20 | }, 21 | "filters": [ 22 | { 23 | "type": "decoder", 24 | "name": "router", 25 | "config": {} 26 | } 27 | ] 28 | } 29 | } 30 | ] 31 | } 32 | ], 33 | "admin": { 34 | "access_log_path": "./log/sfreverseproxy.access_log.admin.log", 35 | "address": "tcp://0.0.0.0:8001" 36 | }, 37 | "cluster_manager": { 38 | "sds": { 39 | "cluster": { 40 | "name": "sds", 41 | "type": "static", 42 | "connect_timeout_ms": 5000, 43 | "lb_type": "round_robin", 44 | "hosts": [ 45 | { 46 | "url": "tcp://127.0.0.1:5000" 47 | } 48 | ] 49 | }, 50 | "refresh_delay_ms": 10000 51 | }, 52 | "clusters": [ 53 | { 54 | "name": "rds", 55 | "type": "static", 56 | "connect_timeout_ms": 5000, 57 | "lb_type": "round_robin", 58 | "hosts": [ 59 | { 60 | "url": "tcp://127.0.0.1:5000" 61 | } 62 | ] 63 | } 64 | ], 65 | "cds": { 66 | "cluster": { 67 | "name": "cds", 68 | "connect_timeout_ms": 5000, 69 | "lb_type": "round_robin", 70 | "type": "static", 71 | "hosts": [ 72 | { 73 | "url": "tcp://127.0.0.1:5000" 74 | } 75 | ] 76 | }, 77 | "refresh_delay_ms": 10000 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /sfintegration/config.windows.secure.json: -------------------------------------------------------------------------------- 1 | { 2 | "listeners": [ 3 | { 4 | "name": "SecureReverseProxyListener", 5 | "address": "tcp://0.0.0.0:19081", 6 | "filters": [ 7 | { 8 | "name": "http_connection_manager", 9 | "config": { 10 | "access_log": [ 11 | { 12 | "path": "./log/sfreverseproxy.access_log.log" 13 | } 14 | ], 15 | "codec_type": "auto", 16 | "stat_prefix": "reverse_proxy_http", 17 | "rds": { 18 | "cluster": "rds", 19 | "route_config_name": "reverse_proxy", 20 | "refresh_delay_ms": 10000 21 | }, 22 | "filters": [ 23 | { 24 | "type": "decoder", 25 | "name": "router", 26 | "config": {} 27 | } 28 | ] 29 | } 30 | } 31 | ], 32 | "ssl_context": { 33 | "cert_chain_file": "/app/sfcerts/ReverseProxyCertThumbprint.crt", 34 | "private_key_file": "/app/sfcerts/ReverseProxyCertThumbprint.prv" 35 | } 36 | } 37 | ], 38 | "admin": { 39 | "access_log_path": "./log/sfreverseproxy.access_log.admin.log", 40 | "address": "tcp://0.0.0.0:8001" 41 | }, 42 | "cluster_manager": { 43 | "sds": { 44 | "cluster": { 45 | "name": "sds", 46 | "type": "static", 47 | "connect_timeout_ms": 5000, 48 | "lb_type": "round_robin", 49 | "hosts": [ 50 | { 51 | "url": "tcp://127.0.0.1:5000" 52 | } 53 | ] 54 | }, 55 | "refresh_delay_ms": 10000 56 | }, 57 | "clusters": [ 58 | { 59 | "name": "rds", 60 | "type": "static", 61 | "connect_timeout_ms": 5000, 62 | "lb_type": "round_robin", 63 | "hosts": [ 64 | { 65 | "url": "tcp://127.0.0.1:5000" 66 | } 67 | ] 68 | } 69 | ], 70 | "cds": { 71 | "cluster": { 72 | "name": "cds", 73 | "connect_timeout_ms": 5000, 74 | "lb_type": "round_robin", 75 | "type": "static", 76 | "hosts": [ 77 | { 78 | "url": "tcp://127.0.0.1:5000" 79 | } 80 | ] 81 | }, 82 | "refresh_delay_ms": 10000 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /sfintegration/envoymodels/EnvoyAccessLogConfig.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using Newtonsoft.Json; 8 | 9 | namespace sfintegration.envoymodels 10 | { 11 | class EnvoyAccessLogConfig 12 | { 13 | public EnvoyAccessLogConfig() 14 | { } 15 | 16 | [JsonProperty] 17 | public string path = "./log/sfreverseproxy.access_log.log"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sfintegration/envoymodels/EnvoyCluster.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using Newtonsoft.Json; 8 | 9 | namespace sfintegration.envoymodels 10 | { 11 | public class EnvoyCluster 12 | { 13 | public EnvoyCluster(string name, int timeout_ms = 10000, bool isHttps = false, EnvoyClusterSslContext context = null) 14 | { 15 | this.name = name; 16 | this.service_name = name; 17 | if (isHttps) 18 | { 19 | ssl_context = context; 20 | } 21 | connect_timeout_ms = timeout_ms; 22 | } 23 | 24 | [JsonProperty] 25 | public string name; 26 | 27 | [JsonProperty] 28 | public string service_name; 29 | 30 | [JsonProperty] 31 | public string type = "sds"; 32 | 33 | [JsonProperty] 34 | public int connect_timeout_ms; 35 | 36 | [JsonProperty] 37 | public string lb_type = "round_robin"; 38 | 39 | [JsonProperty] 40 | public EvoyOutlierDetection outlier_detection = EvoyOutlierDetection.defaultValue; 41 | 42 | [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] 43 | public EnvoyClusterSslContext ssl_context; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /sfintegration/envoymodels/EnvoyFilterConfig.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using Newtonsoft.Json; 8 | using System.Collections.Generic; 9 | 10 | namespace sfintegration.envoymodels 11 | { 12 | class EnvoyFilterConfig 13 | { 14 | public EnvoyFilterConfig(string stat_prefix) 15 | { 16 | this.stat_prefix = stat_prefix; 17 | this.access_log = new List(); 18 | this.access_log.Add(new EnvoyAccessLogConfig()); 19 | } 20 | 21 | [JsonProperty] 22 | public string stat_prefix; 23 | 24 | [JsonProperty] 25 | public List access_log; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sfintegration/envoymodels/EnvoyHTTPFilterConfig.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using System.Collections.Generic; 8 | using Newtonsoft.Json; 9 | 10 | namespace sfintegration.envoymodels 11 | { 12 | class EnvoyHTTPFilterConfig : EnvoyFilterConfig 13 | { 14 | public EnvoyHTTPFilterConfig(string stat_prefix, string cluster) : 15 | base(stat_prefix) 16 | { 17 | rds = new EnvoyRDSConfig(cluster); 18 | filters = new List 19 | { 20 | new { type = "decoder", name = "router", config = new { } } 21 | }; 22 | } 23 | [JsonProperty] 24 | public EnvoyRDSConfig rds; 25 | 26 | [JsonProperty] 27 | public string codec_type = "auto"; 28 | 29 | [JsonProperty] 30 | public List filters; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /sfintegration/envoymodels/EnvoyHTTPListenerFilter.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using Newtonsoft.Json; 8 | 9 | namespace sfintegration.envoymodels 10 | { 11 | class EnvoyHTTPListenerFilter : EnvoyListenerFilter 12 | { 13 | public EnvoyHTTPListenerFilter(string stat_prefix, string cluster) 14 | : base("http_connection_manager") 15 | { 16 | config = new EnvoyHTTPFilterConfig(stat_prefix, cluster); 17 | } 18 | [JsonProperty] 19 | public EnvoyHTTPFilterConfig config; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sfintegration/envoymodels/EnvoyHost.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using Newtonsoft.Json; 8 | using webapi; 9 | 10 | namespace sfintegration.envoymodels 11 | { 12 | public class EnvoyHost 13 | { 14 | public EnvoyHost(string ip_address, int port) 15 | { 16 | this.ip_address = EnvoyDefaults.LocalHostFixup(ip_address); 17 | this.port = port; 18 | } 19 | 20 | [JsonProperty] 21 | public string ip_address; 22 | 23 | [JsonProperty] 24 | public int port; 25 | 26 | [JsonProperty] 27 | public EnvoyHostTags tags = EnvoyHostTags.defaultValue; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /sfintegration/envoymodels/EnvoyHostTags.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using Newtonsoft.Json; 8 | 9 | namespace sfintegration.envoymodels 10 | { 11 | 12 | public class EnvoyHostTags 13 | { 14 | [JsonProperty] 15 | public string az = ""; 16 | 17 | [JsonProperty] 18 | public bool canary = false; 19 | 20 | [JsonProperty] 21 | public int load_balancing_weight = 100; 22 | 23 | public static EnvoyHostTags defaultValue = new EnvoyHostTags(); 24 | } 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /sfintegration/envoymodels/EnvoyListener.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using System.Collections.Generic; 8 | using Newtonsoft.Json; 9 | using webapi; 10 | 11 | namespace sfintegration.envoymodels 12 | { 13 | class EnvoyListener 14 | { 15 | public EnvoyListener(string name, string address, string stat_prefix, List clusters) 16 | { 17 | this.name = name; 18 | this.address = address; 19 | this.filters = new List(); 20 | foreach (var cluster in clusters) 21 | { 22 | if (cluster.Type == ListenerFilterConfig.ListenerFilterType.Tcp) 23 | { 24 | this.filters.Add(new EnvoyTCPListenerFilter(stat_prefix, cluster.ClusterName)); 25 | } 26 | else 27 | { 28 | this.filters.Add(new EnvoyHTTPListenerFilter(stat_prefix, cluster.ClusterName)); 29 | } 30 | } 31 | } 32 | [JsonProperty] 33 | public string name; 34 | 35 | [JsonProperty] 36 | public string address; 37 | 38 | [JsonProperty] 39 | public List filters; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /sfintegration/envoymodels/EnvoyListenerFilter.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using Newtonsoft.Json; 8 | 9 | namespace sfintegration.envoymodels 10 | { 11 | class EnvoyListenerFilter 12 | { 13 | public EnvoyListenerFilter(string filterName) 14 | { 15 | name = filterName; 16 | } 17 | [JsonProperty] 18 | public string name; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sfintegration/envoymodels/EnvoyRDSConfig.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using Newtonsoft.Json; 8 | 9 | namespace sfintegration.envoymodels 10 | { 11 | public class EnvoyRDSConfig 12 | { 13 | public EnvoyRDSConfig(string config_name) 14 | { 15 | route_config_name = config_name; 16 | } 17 | 18 | [JsonProperty] 19 | public string cluster = "rds"; 20 | 21 | [JsonProperty] 22 | public string route_config_name = "gateway"; 23 | 24 | [JsonProperty] 25 | public int refresh_delay_ms = 10000; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sfintegration/envoymodels/EnvoyRetryPolicy.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using Newtonsoft.Json; 8 | 9 | namespace sfintegration.envoymodels 10 | { 11 | public class EnvoyRetryPolicy 12 | { 13 | [JsonProperty] 14 | public string retry_on = "5xx, connect-failure"; 15 | 16 | [JsonProperty] 17 | public int num_retries = 5; 18 | 19 | public static EnvoyRetryPolicy defaultValue = new EnvoyRetryPolicy(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /sfintegration/envoymodels/EnvoyRoute.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using System.Collections.Generic; 8 | using Newtonsoft.Json; 9 | using Newtonsoft.Json.Linq; 10 | 11 | namespace sfintegration.envoymodels 12 | { 13 | public class EnvoyRoute 14 | { 15 | public EnvoyRoute(string cluster, string prefix, string prefix_rewrite, List headers, int timeout_ms = 10000) 16 | { 17 | this.cluster = cluster; 18 | this.prefix = prefix; 19 | this.prefix_rewrite = prefix_rewrite; 20 | if (!prefix.EndsWith("/")) 21 | { 22 | this.prefix += "/"; 23 | } 24 | if (!prefix_rewrite.EndsWith("/")) 25 | { 26 | this.prefix_rewrite += "/"; 27 | } 28 | this.headers = headers; 29 | this.timeout_ms = timeout_ms; 30 | } 31 | 32 | [JsonProperty] 33 | public string cluster; 34 | 35 | [JsonProperty] 36 | public string prefix; 37 | 38 | [JsonProperty] 39 | public string prefix_rewrite; 40 | 41 | [JsonProperty] 42 | public List headers; 43 | public bool ShouldSerializeheaders() 44 | { 45 | return headers != null && headers.Count != 0; 46 | } 47 | 48 | [JsonProperty] 49 | public int timeout_ms; 50 | 51 | [JsonProperty] 52 | public EnvoyRetryPolicy retry_policy = EnvoyRetryPolicy.defaultValue; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /sfintegration/envoymodels/EnvoyTCPFilterConfig.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using Newtonsoft.Json; 8 | 9 | namespace sfintegration.envoymodels 10 | { 11 | class EnvoyTCPFilterConfig : EnvoyFilterConfig 12 | { 13 | public EnvoyTCPFilterConfig(string stat_prefix, string cluster) : base(stat_prefix) 14 | { 15 | route_config = new EnvoyTCPRouteConfig(cluster); 16 | } 17 | [JsonProperty] 18 | public EnvoyTCPRouteConfig route_config; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sfintegration/envoymodels/EnvoyTCPListenerFilter.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using Newtonsoft.Json; 8 | 9 | namespace sfintegration.envoymodels 10 | { 11 | class EnvoyTCPListenerFilter : EnvoyListenerFilter 12 | { 13 | public EnvoyTCPListenerFilter(string stat_prefix, string cluster) 14 | : base("tcp_proxy") 15 | { 16 | config = new EnvoyTCPFilterConfig(stat_prefix, cluster); 17 | } 18 | [JsonProperty] 19 | public EnvoyTCPFilterConfig config; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sfintegration/envoymodels/EnvoyTCPRoute.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using Newtonsoft.Json; 8 | 9 | namespace sfintegration.envoymodels 10 | { 11 | class EnvoyTCPRoute 12 | { 13 | public EnvoyTCPRoute(string cluster) 14 | { 15 | this.cluster = cluster; 16 | } 17 | 18 | [JsonProperty] 19 | public string cluster; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sfintegration/envoymodels/EnvoyTCPRouteConfig.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using System.Collections.Generic; 8 | using Newtonsoft.Json; 9 | 10 | namespace sfintegration.envoymodels 11 | { 12 | class EnvoyTCPRouteConfig 13 | { 14 | public EnvoyTCPRouteConfig(string cluster) 15 | { 16 | routes = new List 17 | { 18 | new EnvoyTCPRoute(cluster) 19 | }; 20 | } 21 | [JsonProperty] 22 | List routes; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sfintegration/envoymodels/EnvoyVirtualHost.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using System; 8 | using System.Collections.Generic; 9 | using Newtonsoft.Json; 10 | 11 | namespace sfintegration.envoymodels 12 | { 13 | public class EnvoyVirtualHost 14 | { 15 | public EnvoyVirtualHost(string name, List domains, List routes) 16 | { 17 | this.name = name; 18 | this.domains = domains; 19 | this.routes = routes; 20 | } 21 | 22 | [JsonProperty] 23 | public string name; 24 | 25 | [JsonProperty] 26 | public List domains; 27 | 28 | [JsonProperty] 29 | public List routes; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /sfintegration/envoymodels/EvoyOutlierDetection.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using Newtonsoft.Json; 8 | 9 | namespace sfintegration.envoymodels 10 | { 11 | public class EvoyOutlierDetection 12 | { 13 | [JsonProperty] 14 | public int consecutive_5xx = 3; 15 | 16 | static public EvoyOutlierDetection defaultValue = new EvoyOutlierDetection(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sfintegration/envoymodels/envoyclustersslcontext.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | using System.Collections.Generic; 8 | using System.IO; 9 | using Newtonsoft.Json; 10 | 11 | namespace sfintegration.envoymodels 12 | { 13 | public class EnvoyClusterSslContext 14 | { 15 | static string ca_cert_file_path = "/var/lib/sfreverseproxycerts/servicecacert.pem"; 16 | public EnvoyClusterSslContext(string verify_certificate_hash, List verify_subject_alt_name) 17 | { 18 | this.verify_certificate_hash = verify_certificate_hash; 19 | this.verify_subject_alt_name = verify_subject_alt_name; 20 | if (File.Exists(ca_cert_file_path)) // change to check for existance of file 21 | { 22 | ca_cert_file = ca_cert_file_path; 23 | } 24 | } 25 | 26 | [JsonProperty] 27 | public string cert_chain_file = "/var/lib/sfreverseproxycerts/reverseproxycert.pem"; 28 | 29 | [JsonProperty] 30 | public string private_key_file = "/var/lib/sfreverseproxycerts/reverseproxykey.pem"; 31 | 32 | [JsonProperty] 33 | public string verify_certificate_hash; 34 | public bool ShouldSerializeverify_certificate_hash() 35 | { 36 | return verify_certificate_hash != null; 37 | } 38 | 39 | [JsonProperty] 40 | public List verify_subject_alt_name; 41 | public bool ShouldSerializeverify_subject_alt_name() 42 | { 43 | return verify_subject_alt_name != null && verify_subject_alt_name.Count != 0; 44 | } 45 | 46 | [JsonProperty] 47 | public string ca_cert_file; 48 | public bool ShouldSerializeca_cert_file() 49 | { 50 | return ca_cert_file != null; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /sfintegration/models/GatewayDestination.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | namespace Gateway.Models 8 | { 9 | using Newtonsoft.Json; 10 | using System.Text; 11 | 12 | public partial class GatewayDestination 13 | { 14 | public GatewayDestination() 15 | { 16 | } 17 | 18 | public GatewayDestination(string applicationName, string serviceName, string endpointName = default(string)) 19 | { 20 | ApplicationName = applicationName; 21 | ServiceName = serviceName; 22 | EndpointName = endpointName; 23 | } 24 | 25 | [JsonProperty(PropertyName = "applicationName")] 26 | public string ApplicationName { get; set; } 27 | 28 | [JsonProperty(PropertyName = "serviceName")] 29 | public string ServiceName { get; set; } 30 | 31 | [JsonProperty(PropertyName = "endpointName")] 32 | public string EndpointName { get; set; } 33 | 34 | public string EnvoyServiceName() 35 | { 36 | StringBuilder sb = new StringBuilder(); 37 | sb.Append(ApplicationName); 38 | sb.Append("_"); 39 | sb.Append(ServiceName); 40 | if (!string.IsNullOrEmpty(EndpointName)) 41 | { 42 | sb.Append("_"); 43 | sb.Append(EndpointName); 44 | } 45 | sb.Append("|*|-2"); 46 | return sb.ToString(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /sfintegration/models/GatewayProperties.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | namespace Gateway.Models 8 | { 9 | using Newtonsoft.Json; 10 | using System.Collections.Generic; 11 | 12 | public class GatewayResourceDescription 13 | { 14 | public GatewayResourceDescription() 15 | { 16 | } 17 | 18 | public GatewayResourceDescription(NetworkRef sourceNetwork, IList tcp, IList http) 19 | { 20 | SourceNetwork = sourceNetwork; 21 | Tcp = tcp; 22 | Http = http; 23 | } 24 | 25 | [JsonProperty(PropertyName = "sourceNetwork")] 26 | public NetworkRef SourceNetwork{ get; set; } 27 | 28 | [JsonProperty(PropertyName = "tcp")] 29 | public IList Tcp { get; set; } 30 | 31 | [JsonProperty(PropertyName = "http")] 32 | public IList Http { get; set; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sfintegration/models/HttpConfig.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | namespace Gateway.Models 8 | { 9 | using Newtonsoft.Json; 10 | using System.Collections.Generic; 11 | 12 | public class HttpConfig 13 | { 14 | public HttpConfig() 15 | { 16 | } 17 | 18 | public HttpConfig(int port, IList hosts, string name = default(string)) 19 | { 20 | Name = name; 21 | Port = port; 22 | Hosts = hosts; 23 | } 24 | 25 | [JsonProperty(PropertyName = "name")] 26 | public string Name { get; set; } 27 | 28 | [JsonProperty(PropertyName = "port")] 29 | public int Port { get; set; } 30 | 31 | [JsonProperty(PropertyName = "hosts")] 32 | public IList Hosts { get; set; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sfintegration/models/HttpHostNameConfig.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | namespace Gateway.Models 8 | { 9 | using Newtonsoft.Json; 10 | using System.Collections.Generic; 11 | 12 | public class HttpHostNameConfig 13 | { 14 | public HttpHostNameConfig() 15 | { 16 | } 17 | 18 | public HttpHostNameConfig(IList routes, string name = default(string)) 19 | { 20 | Name = name; 21 | Routes = routes; 22 | } 23 | 24 | [JsonProperty(PropertyName = "name")] 25 | public string Name { get; set; } 26 | 27 | [JsonProperty(PropertyName = "routes")] 28 | public IList Routes { get; set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sfintegration/models/HttpRouteConfig.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | namespace Gateway.Models 8 | { 9 | using Newtonsoft.Json; 10 | 11 | public class HttpRouteConfig 12 | { 13 | public HttpRouteConfig() 14 | { 15 | Match = new HttpRouteMatchRule(); 16 | } 17 | 18 | public HttpRouteConfig(HttpRouteMatchRule match, GatewayDestination destination, string name = default(string)) 19 | { 20 | Name = name; 21 | Match = match; 22 | Destination = destination; 23 | } 24 | 25 | [JsonProperty(PropertyName = "name")] 26 | public string Name { get; set; } 27 | 28 | [JsonProperty(PropertyName = "match")] 29 | public HttpRouteMatchRule Match { get; set; } 30 | 31 | [JsonProperty(PropertyName = "destination")] 32 | public GatewayDestination Destination { get; set; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sfintegration/models/HttpRouteMatchHeader.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | namespace Gateway.Models 8 | { 9 | using Newtonsoft.Json; 10 | 11 | public class HttpRouteMatchHeader 12 | { 13 | public HttpRouteMatchHeader() 14 | { 15 | } 16 | 17 | public HttpRouteMatchHeader(string name, string value = default(string), string type = default(string)) 18 | { 19 | Name = name; 20 | Value = value; 21 | Type = type; 22 | } 23 | 24 | [JsonProperty(PropertyName = "name")] 25 | public string Name { get; set; } 26 | 27 | [JsonProperty(PropertyName = "value")] 28 | public string Value { get; set; } 29 | 30 | [JsonProperty(PropertyName = "type")] 31 | public string Type { get; set; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sfintegration/models/HttpRouteMatchPath.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | namespace Gateway.Models 8 | { 9 | using Newtonsoft.Json; 10 | 11 | public class HttpRouteMatchPath 12 | { 13 | public HttpRouteMatchPath() 14 | { 15 | } 16 | 17 | public HttpRouteMatchPath(string value, string rewrite = default(string)) 18 | { 19 | Value = value; 20 | Rewrite = rewrite; 21 | } 22 | 23 | static HttpRouteMatchPath() 24 | { 25 | Type = "prefix"; 26 | } 27 | 28 | [JsonProperty(PropertyName = "value")] 29 | public string Value { get; set; } 30 | 31 | [JsonProperty(PropertyName = "rewrite")] 32 | public string Rewrite { get; set; } 33 | 34 | [JsonProperty(PropertyName = "type")] 35 | public static string Type { get; private set; } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sfintegration/models/HttpRouteMatchRule.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | namespace Gateway.Models 8 | { 9 | using Newtonsoft.Json; 10 | using System.Collections.Generic; 11 | 12 | public class HttpRouteMatchRule 13 | { 14 | public HttpRouteMatchRule() 15 | { 16 | Path = new HttpRouteMatchPath(); 17 | } 18 | 19 | public HttpRouteMatchRule(HttpRouteMatchPath path, IList headers = default(IList)) 20 | { 21 | Path = path; 22 | Headers = headers; 23 | } 24 | 25 | [JsonProperty(PropertyName = "path")] 26 | public HttpRouteMatchPath Path { get; set; } 27 | 28 | [JsonProperty(PropertyName = "headers")] 29 | public IList Headers { get; set; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /sfintegration/models/NetworkRef.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | namespace Gateway.Models 8 | { 9 | using Newtonsoft.Json; 10 | 11 | public class NetworkRef 12 | { 13 | public NetworkRef() 14 | { 15 | } 16 | 17 | public NetworkRef(string name = default(string)) 18 | { 19 | Name = name; 20 | } 21 | 22 | [JsonProperty(PropertyName = "name")] 23 | public string Name { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sfintegration/models/TcpConfig.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See License.txt in 4 | // the repo root for license information. 5 | // ------------------------------------------------------------ 6 | 7 | namespace Gateway.Models 8 | { 9 | using Newtonsoft.Json; 10 | 11 | public class TcpConfig 12 | { 13 | public TcpConfig() 14 | { 15 | } 16 | 17 | public TcpConfig(string name, int port, GatewayDestination destination) 18 | { 19 | Name = name; 20 | Port = port; 21 | Destination = destination; 22 | } 23 | 24 | [JsonProperty(PropertyName = "name")] 25 | public string Name { get; set; } 26 | 27 | [JsonProperty(PropertyName = "port")] 28 | public int Port { get; set; } 29 | 30 | [JsonProperty(PropertyName = "destination")] 31 | public GatewayDestination Destination { get; set; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sfintegration/sfintegration.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | ubuntu.16.04-x64;win10-x64 7 | 8 | 9 | 10 | x64 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /sfintegration/startup.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal EnableExtensions EnableDelayedExpansion 3 | 4 | if not exist .\log mkdir .\log 5 | set path=%PATH%;%FabricCodePath% 6 | 7 | echo Environment 8 | set 9 | echo. 10 | 11 | :waitforendpointfile 12 | if not exist "%Fabric_Folder_Application%\Resolver.Endpoints.txt" ( 13 | ping -n 10 127.0.0.1 > nul 14 | goto :waitforendpointfile 15 | ) 16 | 17 | if exist "%Fabric_Folder_Application%\Resolver.Endpoints.txt" ( 18 | for /f "tokens=4 delims=;" %%i in (%Fabric_Folder_Application%\Resolver.Endpoints.txt) do set Fabric_Endpoint_GatewayProxyResolverEndpoint=%%i 19 | ) 20 | 21 | echo Run startup to generate enoy config 22 | startup.exe config.template.json config.gateway.json 23 | 24 | set ENVOYCMD=envoy.exe -c config.gateway.json --service-cluster gateway_proxy --service-node ingress_node -l info 25 | echo %ENVOYCMD% 26 | 27 | %ENVOYCMD% 28 | 29 | echo envoy exited. Sleeping ... 30 | 31 | ping -n 3600 127.0.0.1 > nul 32 | -------------------------------------------------------------------------------- /sfintegration/startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # timestamp function 4 | timestamp() { 5 | echo [$(date +"%Y-%m-%d %H:%M:%S.%3N%Z")][info][startup.sh] 6 | } 7 | 8 | # timestamp function 9 | timestamperror() { 10 | echo [$(date +"%Y-%m-%d %H:%M:%S.%3N%Z")][error][startup.sh] 11 | } 12 | 13 | if [ "${Fabric_Folder_App_Log}" == "" ] 14 | then 15 | Fabric_Folder_App_Log=./log 16 | mkdir ./log 17 | else 18 | ln -s ${Fabric_Folder_App_Log} ./log 19 | fi 20 | reverse_proxy_log_path=${Fabric_Folder_App_Log}/sfreverseproxy.stdout 21 | 22 | cat imagecreationtime.txt | tee -a ${reverse_proxy_log_path}.log 23 | set | tee -a ${reverse_proxy_log_path}.log 24 | 25 | use_https=${UseHttps:-false} 26 | gateway_mode=${GatewayMode:-false} 27 | if [ ${gateway_mode,,} == "true" ] 28 | then 29 | config_file=config.gateway.json 30 | else 31 | if [ ${use_https,,} == "true" ] 32 | then 33 | if [ -z "${ReverseProxyCertThumbprint}" ] 34 | then 35 | echo $(timestamperror) Invalid Reverse Proxy Thumbprint | tee -a ${reverse_proxy_log_path}.log 36 | exit 1 37 | fi 38 | config_file=config.secure.json 39 | else 40 | config_file=config.json 41 | fi 42 | fi 43 | 44 | if [ "${Fabric_NodeName}" == "" ] 45 | then 46 | Fabric_NodeName="standalone" 47 | fi 48 | 49 | echo $(timestamp) LD_LIBRARY_PATH=/opt/microsoft/servicefabric/bin/Fabric/Fabric.Code:. FabricPackageFileName= ./sfintegration | tee -a "${reverse_proxy_log_path}.log" 50 | LD_LIBRARY_PATH=/opt/microsoft/servicefabric/bin/Fabric/Fabric.Code:. FabricPackageFileName= ./sfintegration 2>&1 | tee -a "${reverse_proxy_log_path}.sfintegration.log" & 51 | 52 | echo $(timestamp) Begin validate Envoy cofigfile, $config_file | tee -a ${reverse_proxy_log_path}.log 53 | echo $(timestamp) /usr/local/bin/envoy -c ${config_file} --service-cluster ReverseProxy --service-node ${Fabric_NodeName} --mode validate | tee -a ${reverse_proxy_log_path}.log 54 | /usr/local/bin/envoy --disable-hot-restart -c ${config_file} --service-cluster ReverseProxy --service-node ${Fabric_NodeName} --mode validate 2>&1 | tee -a "${reverse_proxy_log_path}.log" 55 | retval=$? 56 | if [ $retval -eq 1 ] 57 | then 58 | echo $(timestamperror) Failed validate Envoy cofigfile, $config_file | tee -a "${reverse_proxy_log_path}.log" 59 | exit 1 60 | fi 61 | echo $(timestamp) Succeeded validate Envoy cofigfile, $config_file | tee -a "${reverse_proxy_log_path}.log" 62 | 63 | echo $(timestamp) /usr/local/bin/envoy --max-obj-name-len 256 -l info --disable-hot-restart -c ${config_file} --service-cluster ReverseProxy --service-node ${Fabric_NodeName} | tee -a "${reverse_proxy_log_path}.log" 64 | /usr/local/bin/envoy --max-obj-name-len 256 -l info --disable-hot-restart -c ${config_file} --service-cluster ReverseProxy --service-node ${Fabric_NodeName} 2>&1 | tee -a "${reverse_proxy_log_path}.envoy.log" 65 | --------------------------------------------------------------------------------