├── .gitignore ├── .travis.yml ├── ARM-template ├── components │ ├── containerRegistry.json │ ├── containerService.json │ ├── controllerAzureRegistry.json │ ├── controllerCustomRegistry.json │ ├── controllerNode.json │ ├── emptyTemplate.json │ └── storageAccount.json └── param.json ├── LICENSE ├── README.md ├── config ├── nginx-basic.conf ├── nginx-openid.conf └── openidc.lua ├── createUiDefinition.json ├── docker ├── elasticsearch │ ├── Dockerfile │ ├── config │ │ └── elasticsearch.yml │ └── run.sh ├── filebeat │ ├── Dockerfile │ └── filebeat.yml ├── kibana │ ├── Dockerfile │ ├── nginx-site.conf │ └── run.sh ├── logstash │ ├── Dockerfile │ ├── pipeline │ │ └── logstash.conf │ └── run.sh └── push-images.sh ├── helm-charts ├── config.yaml ├── es │ ├── Chart.yaml │ ├── templates │ │ ├── _helpers.tpl │ │ ├── az-storageclass.yaml │ │ ├── es-client-deployment.yaml │ │ ├── es-data-service.yaml │ │ ├── es-data-statefulset.yaml │ │ ├── es-discovery-service.yaml │ │ ├── es-master-deployment.yaml │ │ └── es-service.yaml │ └── values.yaml ├── filebeat │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ │ ├── _helpers.tpl │ │ └── filebeat-daemonset.yaml │ └── values.yaml ├── kibana │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ │ ├── _helpers.tpl │ │ ├── kibana-deployment.yaml │ │ └── kibana-service.yaml │ └── values.yaml ├── logstash │ ├── .helmignore │ ├── Chart.yaml │ ├── config │ │ └── logstash.conf │ ├── templates │ │ ├── _helpers.tpl │ │ ├── logstash-configmap.yaml │ │ ├── logstash-deployment.yaml │ │ └── logstash-service.yaml │ └── values.yaml ├── ns │ ├── Chart.yaml │ ├── templates │ │ └── elk-namespace.yaml │ └── values.yaml └── start-elk.sh ├── image ├── elk-acs-kube-aad-access.png ├── elk-acs-kube-aad-redirect.png └── elk-acs-kube-arch.png ├── mainTemplate.json └── scripts └── run.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.ear 17 | *.tar.gz 18 | *.rar 19 | 20 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 21 | hs_err_pid* 22 | 23 | 24 | .idea/ 25 | elk-acs-kubernetes.iml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: javascript 2 | branches: 3 | only: 4 | - release 5 | install: 6 | # Set up git user name and tag this commit 7 | - git config --local user.name "Auto release" 8 | - git config --local user.email "vschinaauto@microsoft.com" 9 | - export deploy_tag="$(date +'%Y%m%d%H%M%S')-$(git log --format=%h -1)" 10 | script: 11 | - git tag ${deploy_tag} 12 | - zip solution -r config -r docker -r helm-charts 13 | - zip "elk-${deploy_tag}" solution.zip -r scripts -r ARM-template createUiDefinition.json mainTemplate.json 14 | deploy: 15 | provider: releases 16 | api_key: ${github_token} 17 | file: "elk-${deploy_tag}.zip" 18 | skip_cleanup: true 19 | on: 20 | branch: release -------------------------------------------------------------------------------- /ARM-template/components/containerRegistry.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "location": { 6 | "type": "string", 7 | "defaultValue": "[resourceGroup().location]", 8 | "metadata": { 9 | "description": "The location of all components" 10 | } 11 | } 12 | }, 13 | "variables": { 14 | "registryName": "[concat(uniquestring(resourceGroup().id), 'registry')]", 15 | "registryDisk": "[concat(uniquestring(resourceGroup().id), 'elkimage')]" 16 | }, 17 | "resources": [ 18 | { 19 | "apiVersion": "2017-06-01", 20 | "type": "Microsoft.Storage/storageAccounts", 21 | "name": "[variables('registryDisk')]", 22 | "location": "[parameters('location')]", 23 | "sku": { 24 | "name": "Standard_LRS" 25 | }, 26 | "kind": "Storage", 27 | "properties": {} 28 | }, 29 | { 30 | "apiVersion": "2017-03-01", 31 | "name": "[variables('registryName')]", 32 | "type": "Microsoft.ContainerRegistry/registries", 33 | "location": "[parameters('location')]", 34 | "dependsOn": [ 35 | "[variables('registryDisk')]" 36 | ], 37 | "sku": { 38 | "name": "Basic" 39 | }, 40 | "properties": { 41 | "adminUserEnabled": true, 42 | "storageAccount": { 43 | "name": "[variables('registryDisk')]", 44 | "accessKey": "[listKeys(variables('registryDisk'),'2017-06-01').keys[0].value]" 45 | } 46 | } 47 | } 48 | ], 49 | "outputs": {} 50 | } -------------------------------------------------------------------------------- /ARM-template/components/containerService.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "dnsNamePrefix": { 6 | "type": "string", 7 | "metadata": { 8 | "description": "Sets the Domain name prefix for the cluster. The concatenation of the domain name and the regionalized DNS zone make up the fully qualified domain name associated with the public IP address." 9 | } 10 | }, 11 | "agentCount": { 12 | "type": "int", 13 | "defaultValue": 1, 14 | "metadata": { 15 | "description": "The number of agents for the cluster. This value can be from 1 to 100 (note, for Kubernetes clusters you will also get 1 or 2 public agents in addition to these seleted masters)" 16 | }, 17 | "minValue": 1, 18 | "maxValue": 100 19 | }, 20 | "agentVMSize": { 21 | "type": "string", 22 | "defaultValue": "Standard_DS3_v2", 23 | "allowedValues": [ 24 | "Standard_A0", 25 | "Standard_A1", 26 | "Standard_A2", 27 | "Standard_A3", 28 | "Standard_A4", 29 | "Standard_A1_v2", 30 | "Standard_A2_v2", 31 | "Standard_A4_v2", 32 | "Standard_A8_v2", 33 | "Standard_A2m_v2", 34 | "Standard_A4m_v2", 35 | "Standard_A8m_v2", 36 | "Standard_D2", 37 | "Standard_D3", 38 | "Standard_D4", 39 | "Standard_D11", 40 | "Standard_D12", 41 | "Standard_D13", 42 | "Standard_D14", 43 | "Standard_D2_v2", 44 | "Standard_D3_v2", 45 | "Standard_D4_v2", 46 | "Standard_D5_v2", 47 | "Standard_D11_v2", 48 | "Standard_D12_v2", 49 | "Standard_D13_v2", 50 | "Standard_D14_v2", 51 | "Standard_DS2", 52 | "Standard_DS3", 53 | "Standard_DS4", 54 | "Standard_DS11", 55 | "Standard_DS12", 56 | "Standard_DS13", 57 | "Standard_DS14", 58 | "Standard_DS2_v2", 59 | "Standard_DS3_v2", 60 | "Standard_DS4_v2", 61 | "Standard_DS5_v2", 62 | "Standard_DS11_v2", 63 | "Standard_DS12_v2", 64 | "Standard_DS13_v2", 65 | "Standard_DS14_v2", 66 | "Standard_G1", 67 | "Standard_G2", 68 | "Standard_G3", 69 | "Standard_G4", 70 | "Standard_G5", 71 | "Standard_GS1", 72 | "Standard_GS2", 73 | "Standard_GS3", 74 | "Standard_GS4", 75 | "Standard_GS5" 76 | ], 77 | "metadata": { 78 | "description": "The size of the Virtual Machine as agent." 79 | } 80 | }, 81 | "linuxAdminUsername": { 82 | "type": "string", 83 | "defaultValue": "azureuser", 84 | "metadata": { 85 | "description": "User name for the Linux Virtual Machines." 86 | } 87 | }, 88 | "masterCount": { 89 | "type": "int", 90 | "defaultValue": 1, 91 | "allowedValues": [ 92 | 1, 93 | 3, 94 | 5 95 | ], 96 | "metadata": { 97 | "description": "The number of Kubernetes masters for the cluster." 98 | } 99 | }, 100 | "sshRSAPublicKey": { 101 | "type": "string", 102 | "metadata": { 103 | "description": "Configure all linux machines with the SSH RSA public key string. Your key should include three parts, for example 'ssh-rsa AAAAB...snip...UcyupgH azureuser@linuxvm'" 104 | } 105 | }, 106 | "servicePrincipalClientId": { 107 | "metadata": { 108 | "description": "Client ID (used by cloudprovider)" 109 | }, 110 | "type": "securestring" 111 | }, 112 | "servicePrincipalClientSecret": { 113 | "metadata": { 114 | "description": "The Service Principal Client Secret." 115 | }, 116 | "type": "securestring" 117 | }, 118 | "location": { 119 | "type": "string", 120 | "defaultValue": "[resourceGroup().location]", 121 | "metadata": { 122 | "description": "The location of all components" 123 | } 124 | } 125 | }, 126 | "variables": { 127 | "adminUsername": "[parameters('linuxAdminUsername')]", 128 | "agentCount": "[parameters('agentCount')]", 129 | "agentsEndpointDNSNamePrefix": "[concat(parameters('dnsNamePrefix'), 'agents')]", 130 | "agentVMSize": "[parameters('agentVMSize')]", 131 | "masterCount": "[parameters('masterCount')]", 132 | "mastersEndpointDNSNamePrefix": "[concat(parameters('dnsNamePrefix'), 'master')]", 133 | "orchestratorType": "Kubernetes", 134 | "sshRSAPublicKey": "[parameters('sshRSAPublicKey')]", 135 | "servicePrincipalClientId": "[parameters('servicePrincipalClientId')]", 136 | "servicePrincipalClientSecret": "[parameters('servicePrincipalClientSecret')]" 137 | }, 138 | "resources": [ 139 | { 140 | "apiVersion": "2017-01-31", 141 | "type": "Microsoft.ContainerService/containerServices", 142 | "location": "[parameters('location')]", 143 | "name": "[concat('containerservice-',resourceGroup().name)]", 144 | "properties": { 145 | "orchestratorProfile": { 146 | "orchestratorType": "[variables('orchestratorType')]" 147 | }, 148 | "masterProfile": { 149 | "count": "[variables('masterCount')]", 150 | "dnsPrefix": "[variables('mastersEndpointDNSNamePrefix')]" 151 | }, 152 | "agentPoolProfiles": [ 153 | { 154 | "name": "agentpools", 155 | "count": "[variables('agentCount')]", 156 | "vmSize": "[variables('agentVMSize')]", 157 | "dnsPrefix": "[variables('agentsEndpointDNSNamePrefix')]" 158 | } 159 | ], 160 | "linuxProfile": { 161 | "adminUsername": "[variables('adminUsername')]", 162 | "ssh": { 163 | "publicKeys": [ 164 | { 165 | "keyData": "[variables('sshRSAPublicKey')]" 166 | } 167 | ] 168 | } 169 | }, 170 | "servicePrincipalProfile": { 171 | "clientId": "[variables('servicePrincipalClientId')]", 172 | "secret": "[variables('servicePrincipalClientSecret')]" 173 | } 174 | } 175 | } 176 | ], 177 | "outputs": { 178 | "masterFQDN": { 179 | "type": "string", 180 | "value": "[reference(concat('Microsoft.ContainerService/containerServices/', 'containerservice-', resourceGroup().name)).masterProfile.fqdn]" 181 | }, 182 | "sshMaster0": { 183 | "type": "string", 184 | "value": "[concat('ssh ', variables('adminUsername'), '@', reference(concat('Microsoft.ContainerService/containerServices/', 'containerservice-', resourceGroup().name)).masterProfile.fqdn, ' -A -p 22')]" 185 | }, 186 | "agentFQDN": { 187 | "type": "string", 188 | "value": "[reference(concat('Microsoft.ContainerService/containerServices/', 'containerservice-', resourceGroup().name)).agentPoolProfiles[0].fqdn]" 189 | } 190 | } 191 | } -------------------------------------------------------------------------------- /ARM-template/components/controllerAzureRegistry.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "dnsNamePrefix": { 6 | "type": "string", 7 | "metadata": { 8 | "description": "Sets the Domain name prefix for the cluster. The concatenation of the domain name and the regionalized DNS zone make up the fully qualified domain name associated with the public IP address." 9 | } 10 | }, 11 | "linuxAdminUsername": { 12 | "type": "string", 13 | "defaultValue": "azureuser", 14 | "metadata": { 15 | "description": "User name for the Linux Virtual Machines." 16 | } 17 | }, 18 | "adminPassword": { 19 | "type": "securestring", 20 | "metadata": { 21 | "description": "Password to login controller node" 22 | } 23 | }, 24 | "ubuntuOSVersion": { 25 | "type": "string", 26 | "defaultValue": "16.04.0-LTS", 27 | "allowedValues": [ 28 | "12.04.5-LTS", 29 | "14.04.5-LTS", 30 | "16.04.0-LTS" 31 | ], 32 | "metadata": { 33 | "description": "The Ubuntu version for the controller node. This will pick a fully patched image of this given Ubuntu version." 34 | } 35 | }, 36 | "privateKey": { 37 | "type": "securestring", 38 | "metadata": { 39 | "description": "Base64 encoded private key corresbonding to the public key for ACS" 40 | } 41 | }, 42 | "location": { 43 | "type": "string", 44 | "defaultValue": "[resourceGroup().location]", 45 | "metadata": { 46 | "description": "The location of all components" 47 | } 48 | }, 49 | "registryUrl": { 50 | "type": "string", 51 | "defaultValue": "", 52 | "metadata": { 53 | "description": "Custom registry(e.g. Docker Hub) url to be used" 54 | } 55 | }, 56 | "eventHubNamespace": { 57 | "type": "string", 58 | "defaultValue": "undefined", 59 | "metadata": { 60 | "description": "The target event hub namespace" 61 | } 62 | }, 63 | "eventHubKeyName": { 64 | "type": "string", 65 | "defaultValue": "undefined", 66 | "metadata": { 67 | "description": "The name of the shared access policy" 68 | } 69 | }, 70 | "eventHubKey": { 71 | "type": "string", 72 | "defaultValue": "undefined", 73 | "metadata": { 74 | "description": "The shared access key to the target event hub" 75 | } 76 | }, 77 | "eventHubEntityPath": { 78 | "type": "string", 79 | "defaultValue": "undefined", 80 | "metadata": { 81 | "description": "The target event hub name" 82 | } 83 | }, 84 | "eventHubPartitionnumber": { 85 | "type": "string", 86 | "defaultValue": "4", 87 | "metadata": { 88 | "description": "Partition count of the target event hub" 89 | } 90 | }, 91 | "eventHubThreadWaitSec": { 92 | "type": "string", 93 | "defaultValue": "10", 94 | "metadata": { 95 | "description": "Logstash event hub plugin thread wait interval in seconds" 96 | } 97 | }, 98 | "baseTemplateUrl": { 99 | "type": "string", 100 | "defaultValue": "[uri(deployment().properties.templateLink.uri, '.')]", 101 | "metadata": { 102 | "description": "base URL of templates, typically a repository for configuraions" 103 | } 104 | }, 105 | "storageAccountSku": { 106 | "type": "string", 107 | "defaultValue": "Standard_LRS", 108 | "allowedValues": [ 109 | "Standard_LRS", 110 | "Standard_GRS", 111 | "Standard_RAGRS", 112 | "Standard_ZRS", 113 | "Premium_LRS" 114 | ], 115 | "metadata": { 116 | "description": "Storage account sku to be used as Elasticsearch data node" 117 | } 118 | }, 119 | "archiveUrl": { 120 | "type": "string", 121 | "defaultValue": "[uri(deployment().properties.templateLink.uri, 'solution.zip')]", 122 | "metadata": { 123 | "description": "Archive URL of all contents." 124 | } 125 | }, 126 | "directoryName": { 127 | "type": "string", 128 | "defaultValue": "", 129 | "metadata": { 130 | "description": "Directory in archive." 131 | } 132 | }, 133 | "authenticationMode": { 134 | "type": "string", 135 | "defaultValue": "BasicAuth", 136 | "allowedValues": [ 137 | "BasicAuth", 138 | "AzureAD" 139 | ], 140 | "metadata": { 141 | "description": "User authentication mode." 142 | } 143 | }, 144 | "azureAdClientId": { 145 | "type": "string", 146 | "defaultValue": "undefined", 147 | "metadata": { 148 | "description": "Azure AD client ID(Application ID)" 149 | } 150 | }, 151 | "azureAdClientSecret": { 152 | "type": "securestring", 153 | "defaultValue": "undefined", 154 | "metadata": { 155 | "description": "Azure AD client secret/key" 156 | } 157 | }, 158 | "tenant": { 159 | "type": "string", 160 | "defaultValue": "undefined", 161 | "metadata": { 162 | "description": "Azure AD tenant(e.g. contoso.onmicrosoft.com)" 163 | } 164 | }, 165 | "servicePrincipalClientId": { 166 | "metadata": { 167 | "description": "Client ID (used by cloudprovider)" 168 | }, 169 | "type": "securestring" 170 | }, 171 | "servicePrincipalClientSecret": { 172 | "metadata": { 173 | "description": "The Service Principal Client Secret." 174 | }, 175 | "type": "securestring" 176 | } 177 | }, 178 | "variables": { 179 | "adminUsername": "[parameters('linuxAdminUsername')]", 180 | "mastersEndpointDNSNamePrefix": "[concat(parameters('dnsNamePrefix'), 'master')]", 181 | "controllerDNSNamePrefix": "[concat(parameters('dnsNamePrefix'), 'control')]", 182 | "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'ctl')]", 183 | "frontEndNSGName": "[concat(uniquestring(resourceGroup().id), 'frontEndNSG')]", 184 | "imagePublisher": "Canonical", 185 | "imageOffer": "UbuntuServer", 186 | "nicName": "controllernic", 187 | "addressPrefix": "10.0.0.0/16", 188 | "subnetName": "Subnet", 189 | "subnetPrefix": "10.0.0.0/24", 190 | "storageAccountType": "Standard_LRS", 191 | "publicIPAddressName": "controllerip", 192 | "publicIPAddressType": "Dynamic", 193 | "vmName": "controllervm", 194 | "vmSize": "Standard_D1_v2", 195 | "virtualNetworkName": "controller-vnet", 196 | "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]", 197 | "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", 198 | "dataStorageAccountName": "[concat(uniquestring(resourceGroup().id), 'data')]", 199 | "registryName": "[concat(uniquestring(resourceGroup().id), 'registry')]", 200 | "basedPrivateKey": "[parameters('privateKey')]", 201 | "baseUrl": "[parameters('baseTemplateUrl')]", 202 | "singleQuote": "'" 203 | }, 204 | "resources": [ 205 | { 206 | "apiVersion": "2017-03-30", 207 | "type": "Microsoft.Compute/virtualMachines", 208 | "name": "[variables('vmName')]", 209 | "location": "[parameters('location')]", 210 | "properties": { 211 | "hardwareProfile": { 212 | "vmSize": "[variables('vmSize')]" 213 | }, 214 | "osProfile": { 215 | "computerName": "[variables('vmName')]", 216 | "adminUsername": "[parameters('linuxAdminUsername')]", 217 | "adminPassword": "[parameters('adminPassword')]" 218 | }, 219 | "storageProfile": { 220 | "imageReference": { 221 | "publisher": "[variables('imagePublisher')]", 222 | "offer": "[variables('imageOffer')]", 223 | "sku": "[parameters('ubuntuOSVersion')]", 224 | "version": "latest" 225 | }, 226 | "osDisk": { 227 | "createOption": "FromImage" 228 | }, 229 | "dataDisks": [ 230 | { 231 | "diskSizeGB": "63", 232 | "lun": 0, 233 | "createOption": "Empty" 234 | } 235 | ] 236 | }, 237 | "networkProfile": { 238 | "networkInterfaces": [ 239 | { 240 | "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]" 241 | } 242 | ] 243 | }, 244 | "diagnosticsProfile": { 245 | "bootDiagnostics": { 246 | "enabled": "true", 247 | "storageUri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName')), '2016-01-01').primaryEndpoints.blob)]" 248 | } 249 | } 250 | }, 251 | "resources": [ 252 | { 253 | "name": "config-app", 254 | "type": "extensions", 255 | "location": "[parameters('location')]", 256 | "apiVersion": "2015-06-15", 257 | "dependsOn": [ 258 | "[resourceId('Microsoft.Compute/virtualMachines/', variables('vmName'))]" 259 | ], 260 | "properties": { 261 | "publisher": "Microsoft.Azure.Extensions", 262 | "type": "CustomScript", 263 | "typeHandlerVersion": "2.0", 264 | "autoUpgradeMinorVersion": true, 265 | "settings": { 266 | "fileUris": [ 267 | "[concat(variables('baseUrl'), '/scripts/run.sh')]" 268 | ] 269 | }, 270 | "protectedSettings": { 271 | "commandToExecute": "[concat('bash run.sh -d ', variables('mastersEndpointDNSNamePrefix'), ' -l ', parameters('location'), ' -u ', parameters('linuxAdminUsername'), ' -p ', variables('singleQuote'), parameters('adminPassword'), variables('singleQuote'), ' -k ', variables('basedPrivateKey'), ' -a ', variables('registryName'), ' -b ', listCredentials(resourceId('Microsoft.ContainerRegistry/registries', variables('registryName')), '2017-03-01').passwords[0].value, ' -j ', parameters('eventHubNamespace'), ' -q ', parameters('eventHubKeyName'), ' -m ', parameters('eventHubKey'), ' -n ', parameters('eventHubEntityPath'), ' -o ', parameters('eventHubPartitionnumber'), ' -v ', parameters('eventHubThreadWaitSec'), ' -s ', variables('dataStorageAccountName'), ' -c ', parameters('storageAccountSku'), ' -e ', parameters('archiveUrl'), ' -f ', variables('singleQuote'), parameters('directoryName'), variables('singleQuote'), ' -g ', parameters('authenticationMode'), ' -h ', parameters('azureAdClientId'), ' -i ', parameters('azureAdClientSecret'), ' -t ', parameters('tenant'), ' -w ', parameters('servicePrincipalClientId'), ' -x ', parameters('servicePrincipalClientSecret'), ' -y ', subscription().tenantId, ' -z ', subscription().subscriptionId, ' -R ', resourceGroup().name)]" 272 | } 273 | } 274 | } 275 | ] 276 | } 277 | ] 278 | } -------------------------------------------------------------------------------- /ARM-template/components/controllerCustomRegistry.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "dnsNamePrefix": { 6 | "type": "string", 7 | "metadata": { 8 | "description": "Sets the Domain name prefix for the cluster. The concatenation of the domain name and the regionalized DNS zone make up the fully qualified domain name associated with the public IP address." 9 | } 10 | }, 11 | "linuxAdminUsername": { 12 | "type": "string", 13 | "defaultValue": "azureuser", 14 | "metadata": { 15 | "description": "User name for the Linux Virtual Machines." 16 | } 17 | }, 18 | "adminPassword": { 19 | "type": "securestring", 20 | "metadata": { 21 | "description": "Password to login controller node" 22 | } 23 | }, 24 | "ubuntuOSVersion": { 25 | "type": "string", 26 | "defaultValue": "16.04.0-LTS", 27 | "allowedValues": [ 28 | "12.04.5-LTS", 29 | "14.04.5-LTS", 30 | "16.04.0-LTS" 31 | ], 32 | "metadata": { 33 | "description": "The Ubuntu version for the controller node. This will pick a fully patched image of this given Ubuntu version." 34 | } 35 | }, 36 | "privateKey": { 37 | "type": "securestring", 38 | "metadata": { 39 | "description": "Base64 encoded private key corresbonding to the public key for ACS" 40 | } 41 | }, 42 | "location": { 43 | "type": "string", 44 | "defaultValue": "[resourceGroup().location]", 45 | "metadata": { 46 | "description": "The location of all components" 47 | } 48 | }, 49 | "registryUrl": { 50 | "type": "string", 51 | "defaultValue": "", 52 | "metadata": { 53 | "description": "Custom registry(e.g. Docker Hub) url to be used" 54 | } 55 | }, 56 | "eventHubNamespace": { 57 | "type": "string", 58 | "defaultValue": "undefined", 59 | "metadata": { 60 | "description": "The target event hub namespace" 61 | } 62 | }, 63 | "eventHubKeyName": { 64 | "type": "string", 65 | "defaultValue": "undefined", 66 | "metadata": { 67 | "description": "The name of the shared access policy" 68 | } 69 | }, 70 | "eventHubKey": { 71 | "type": "string", 72 | "defaultValue": "undefined", 73 | "metadata": { 74 | "description": "The shared access key to the target event hub" 75 | } 76 | }, 77 | "eventHubEntityPath": { 78 | "type": "string", 79 | "defaultValue": "undefined", 80 | "metadata": { 81 | "description": "The target event hub name" 82 | } 83 | }, 84 | "eventHubPartitionnumber": { 85 | "type": "string", 86 | "defaultValue": "4", 87 | "metadata": { 88 | "description": "Partition count of the target event hub" 89 | } 90 | }, 91 | "eventHubThreadWaitSec": { 92 | "type": "string", 93 | "defaultValue": "10", 94 | "metadata": { 95 | "description": "Logstash event hub plugin thread wait interval in seconds" 96 | } 97 | }, 98 | "baseTemplateUrl": { 99 | "type": "string", 100 | "defaultValue": "[uri(deployment().properties.templateLink.uri, '.')]", 101 | "metadata": { 102 | "description": "base URL of templates, typically a repository for configuraions" 103 | } 104 | }, 105 | "storageAccountSku": { 106 | "type": "string", 107 | "defaultValue": "Standard_LRS", 108 | "allowedValues": [ 109 | "Standard_LRS", 110 | "Standard_GRS", 111 | "Standard_RAGRS", 112 | "Standard_ZRS", 113 | "Premium_LRS" 114 | ], 115 | "metadata": { 116 | "description": "Storage account sku to be used as Elasticsearch data node" 117 | } 118 | }, 119 | "archiveUrl": { 120 | "type": "string", 121 | "defaultValue": "[uri(deployment().properties.templateLink.uri, 'solution.zip')]", 122 | "metadata": { 123 | "description": "Archive URL of all contents." 124 | } 125 | }, 126 | "directoryName": { 127 | "type": "string", 128 | "defaultValue": "", 129 | "metadata": { 130 | "description": "Directory in archive." 131 | } 132 | }, 133 | "authenticationMode": { 134 | "type": "string", 135 | "defaultValue": "BasicAuth", 136 | "allowedValues": [ 137 | "BasicAuth", 138 | "AzureAD" 139 | ], 140 | "metadata": { 141 | "description": "User authentication mode." 142 | } 143 | }, 144 | "azureAdClientId": { 145 | "type": "string", 146 | "defaultValue": "undefined", 147 | "metadata": { 148 | "description": "Azure AD client ID(Application ID)" 149 | } 150 | }, 151 | "azureAdClientSecret": { 152 | "type": "securestring", 153 | "defaultValue": "undefined", 154 | "metadata": { 155 | "description": "Azure AD client secret/key" 156 | } 157 | }, 158 | "tenant": { 159 | "type": "string", 160 | "defaultValue": "undefined", 161 | "metadata": { 162 | "description": "Azure AD tenant(e.g. contoso.onmicrosoft.com)" 163 | } 164 | }, 165 | "servicePrincipalClientId": { 166 | "metadata": { 167 | "description": "Client ID (used by cloudprovider)" 168 | }, 169 | "type": "securestring" 170 | }, 171 | "servicePrincipalClientSecret": { 172 | "metadata": { 173 | "description": "The Service Principal Client Secret." 174 | }, 175 | "type": "securestring" 176 | } 177 | }, 178 | "variables": { 179 | "adminUsername": "[parameters('linuxAdminUsername')]", 180 | "mastersEndpointDNSNamePrefix": "[concat(parameters('dnsNamePrefix'), 'master')]", 181 | "controllerDNSNamePrefix": "[concat(parameters('dnsNamePrefix'), 'control')]", 182 | "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'ctl')]", 183 | "frontEndNSGName": "[concat(uniquestring(resourceGroup().id), 'frontEndNSG')]", 184 | "imagePublisher": "Canonical", 185 | "imageOffer": "UbuntuServer", 186 | "nicName": "controllernic", 187 | "addressPrefix": "10.0.0.0/16", 188 | "subnetName": "Subnet", 189 | "subnetPrefix": "10.0.0.0/24", 190 | "storageAccountType": "Standard_LRS", 191 | "publicIPAddressName": "controllerip", 192 | "publicIPAddressType": "Dynamic", 193 | "vmName": "controllervm", 194 | "vmSize": "Standard_D1_v2", 195 | "virtualNetworkName": "controller-vnet", 196 | "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]", 197 | "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", 198 | "dataStorageAccountName": "[concat(uniquestring(resourceGroup().id), 'data')]", 199 | "registryName": "[concat(uniquestring(resourceGroup().id), 'registry')]", 200 | "basedPrivateKey": "[parameters('privateKey')]", 201 | "baseUrl": "[parameters('baseTemplateUrl')]", 202 | "singleQuote": "'" 203 | }, 204 | "resources": [ 205 | { 206 | "apiVersion": "2017-03-30", 207 | "type": "Microsoft.Compute/virtualMachines", 208 | "name": "[variables('vmName')]", 209 | "location": "[parameters('location')]", 210 | "properties": { 211 | "hardwareProfile": { 212 | "vmSize": "[variables('vmSize')]" 213 | }, 214 | "osProfile": { 215 | "computerName": "[variables('vmName')]", 216 | "adminUsername": "[parameters('linuxAdminUsername')]", 217 | "adminPassword": "[parameters('adminPassword')]" 218 | }, 219 | "storageProfile": { 220 | "imageReference": { 221 | "publisher": "[variables('imagePublisher')]", 222 | "offer": "[variables('imageOffer')]", 223 | "sku": "[parameters('ubuntuOSVersion')]", 224 | "version": "latest" 225 | }, 226 | "osDisk": { 227 | "createOption": "FromImage" 228 | }, 229 | "dataDisks": [ 230 | { 231 | "diskSizeGB": "63", 232 | "lun": 0, 233 | "createOption": "Empty" 234 | } 235 | ] 236 | }, 237 | "networkProfile": { 238 | "networkInterfaces": [ 239 | { 240 | "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]" 241 | } 242 | ] 243 | }, 244 | "diagnosticsProfile": { 245 | "bootDiagnostics": { 246 | "enabled": "true", 247 | "storageUri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName')), '2016-01-01').primaryEndpoints.blob)]" 248 | } 249 | } 250 | }, 251 | "resources": [ 252 | { 253 | "name": "config-app", 254 | "type": "extensions", 255 | "location": "[parameters('location')]", 256 | "apiVersion": "2015-06-15", 257 | "dependsOn": [ 258 | "[resourceId('Microsoft.Compute/virtualMachines/', variables('vmName'))]" 259 | ], 260 | "properties": { 261 | "publisher": "Microsoft.Azure.Extensions", 262 | "type": "CustomScript", 263 | "typeHandlerVersion": "2.0", 264 | "autoUpgradeMinorVersion": true, 265 | "settings": { 266 | "fileUris": [ 267 | "[concat(variables('baseUrl'), '/scripts/run.sh')]" 268 | ] 269 | }, 270 | "protectedSettings": { 271 | "commandToExecute": "[concat('bash run.sh -d ', variables('mastersEndpointDNSNamePrefix'), ' -l ', parameters('location'), ' -u ', parameters('linuxAdminUsername'), ' -p ', variables('singleQuote'), parameters('adminPassword'), variables('singleQuote'), ' -k ', variables('basedPrivateKey'), ' -r ', parameters('registryUrl'), ' -j ', parameters('eventHubNamespace'), ' -q ', parameters('eventHubKeyName'), ' -m ', parameters('eventHubKey'), ' -n ', parameters('eventHubEntityPath'), ' -o ', parameters('eventHubPartitionnumber'), ' -v ', parameters('eventHubThreadWaitSec'), ' -s ', variables('dataStorageAccountName'), ' -c ', parameters('storageAccountSku'), ' -e ', parameters('archiveUrl'), ' -f ', variables('singleQuote'), parameters('directoryName'), variables('singleQuote'), ' -g ', parameters('authenticationMode'), ' -h ', parameters('azureAdClientId'), ' -i ', parameters('azureAdClientSecret'), ' -t ', parameters('tenant'), ' -w ', parameters('servicePrincipalClientId'), ' -x ', parameters('servicePrincipalClientSecret'), ' -y ', subscription().tenantId, ' -z ', subscription().subscriptionId, ' -R ', resourceGroup().name)]" 272 | } 273 | } 274 | } 275 | ] 276 | } 277 | ] 278 | } -------------------------------------------------------------------------------- /ARM-template/components/controllerNode.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "dnsNamePrefix": { 6 | "type": "string", 7 | "metadata": { 8 | "description": "Sets the Domain name prefix for the cluster. The concatenation of the domain name and the regionalized DNS zone make up the fully qualified domain name associated with the public IP address." 9 | } 10 | }, 11 | "linuxAdminUsername": { 12 | "type": "string", 13 | "defaultValue": "azureuser", 14 | "metadata": { 15 | "description": "User name for the Linux Virtual Machines." 16 | } 17 | }, 18 | "adminPassword": { 19 | "type": "securestring", 20 | "metadata": { 21 | "description": "Password to login controller node" 22 | } 23 | }, 24 | "ubuntuOSVersion": { 25 | "type": "string", 26 | "defaultValue": "16.04.0-LTS", 27 | "allowedValues": [ 28 | "12.04.5-LTS", 29 | "14.04.5-LTS", 30 | "16.04.0-LTS" 31 | ], 32 | "metadata": { 33 | "description": "The Ubuntu version for the controller node. This will pick a fully patched image of this given Ubuntu version." 34 | } 35 | }, 36 | "privateKey": { 37 | "type": "securestring", 38 | "metadata": { 39 | "description": "Base64 encoded private key corresbonding to the public key for ACS" 40 | } 41 | }, 42 | "location": { 43 | "type": "string", 44 | "defaultValue": "[resourceGroup().location]", 45 | "metadata": { 46 | "description": "The location of all components" 47 | } 48 | }, 49 | "registryUrl": { 50 | "type": "string", 51 | "defaultValue": "", 52 | "metadata": { 53 | "description": "Custom registry(e.g. Docker Hub) url to be used" 54 | } 55 | }, 56 | "eventHubNamespace": { 57 | "type": "string", 58 | "defaultValue": "undefined", 59 | "metadata": { 60 | "description": "The target event hub namespace" 61 | } 62 | }, 63 | "eventHubKeyName": { 64 | "type": "string", 65 | "defaultValue": "undefined", 66 | "metadata": { 67 | "description": "The name of the shared access policy" 68 | } 69 | }, 70 | "eventHubKey": { 71 | "type": "string", 72 | "defaultValue": "undefined", 73 | "metadata": { 74 | "description": "The shared access key to the target event hub" 75 | } 76 | }, 77 | "eventHubEntityPath": { 78 | "type": "string", 79 | "defaultValue": "undefined", 80 | "metadata": { 81 | "description": "The target event hub name" 82 | } 83 | }, 84 | "eventHubPartitionnumber": { 85 | "type": "string", 86 | "defaultValue": "4", 87 | "metadata": { 88 | "description": "Partition count of the target event hub" 89 | } 90 | }, 91 | "eventHubThreadWaitSec": { 92 | "type": "string", 93 | "defaultValue": "10", 94 | "metadata": { 95 | "description": "Logstash event hub plugin thread wait interval in seconds" 96 | } 97 | }, 98 | "baseTemplateUrl": { 99 | "type": "string", 100 | "defaultValue": "[uri(deployment().properties.templateLink.uri, '.')]", 101 | "metadata": { 102 | "description": "base URL of templates, typically a repository for configuraions" 103 | } 104 | }, 105 | "storageAccountSku": { 106 | "type": "string", 107 | "defaultValue": "Standard_LRS", 108 | "allowedValues": [ 109 | "Standard_LRS", 110 | "Standard_GRS", 111 | "Standard_RAGRS", 112 | "Standard_ZRS", 113 | "Premium_LRS" 114 | ], 115 | "metadata": { 116 | "description": "Storage account sku to be used as Elasticsearch data node" 117 | } 118 | }, 119 | "archiveUrl": { 120 | "type": "string", 121 | "defaultValue": "[uri(deployment().properties.templateLink.uri, 'solution.zip')]", 122 | "metadata": { 123 | "description": "Archive URL of all contents." 124 | } 125 | }, 126 | "directoryName": { 127 | "type": "string", 128 | "defaultValue": "", 129 | "metadata": { 130 | "description": "Directory in archive." 131 | } 132 | }, 133 | "authenticationMode": { 134 | "type": "string", 135 | "defaultValue": "BasicAuth", 136 | "allowedValues": [ 137 | "BasicAuth", 138 | "AzureAD" 139 | ], 140 | "metadata": { 141 | "description": "User authentication mode." 142 | } 143 | }, 144 | "azureAdClientId": { 145 | "type": "string", 146 | "defaultValue": "undefined", 147 | "metadata": { 148 | "description": "Azure AD client ID(Application ID)" 149 | } 150 | }, 151 | "azureAdClientSecret": { 152 | "type": "securestring", 153 | "defaultValue": "undefined", 154 | "metadata": { 155 | "description": "Azure AD client secret/key" 156 | } 157 | }, 158 | "tenant": { 159 | "type": "string", 160 | "defaultValue": "undefined", 161 | "metadata": { 162 | "description": "Azure AD tenant(e.g. contoso.onmicrosoft.com)" 163 | } 164 | }, 165 | "servicePrincipalClientId": { 166 | "metadata": { 167 | "description": "Client ID (used by cloudprovider)" 168 | }, 169 | "type": "securestring" 170 | }, 171 | "servicePrincipalClientSecret": { 172 | "metadata": { 173 | "description": "The Service Principal Client Secret." 174 | }, 175 | "type": "securestring" 176 | } 177 | }, 178 | "variables": { 179 | "adminUsername": "[parameters('linuxAdminUsername')]", 180 | "mastersEndpointDNSNamePrefix": "[concat(parameters('dnsNamePrefix'), 'master')]", 181 | "controllerDNSNamePrefix": "[concat(parameters('dnsNamePrefix'), 'control')]", 182 | "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'ctl')]", 183 | "frontEndNSGName": "[concat(uniquestring(resourceGroup().id), 'frontEndNSG')]", 184 | "imagePublisher": "Canonical", 185 | "imageOffer": "UbuntuServer", 186 | "nicName": "controllernic", 187 | "addressPrefix": "10.0.0.0/16", 188 | "subnetName": "Subnet", 189 | "subnetPrefix": "10.0.0.0/24", 190 | "storageAccountType": "Standard_LRS", 191 | "publicIPAddressName": "controllerip", 192 | "publicIPAddressType": "Dynamic", 193 | "vmName": "controllervm", 194 | "vmSize": "Standard_A1", 195 | "virtualNetworkName": "controller-vnet", 196 | "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]", 197 | "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", 198 | "dataStorageAccountName": "[concat(uniquestring(resourceGroup().id), 'data')]", 199 | "registryName": "[concat(uniquestring(resourceGroup().id), 'registry')]", 200 | "basedPrivateKey": "[parameters('privateKey')]", 201 | "baseUrl": "[concat(parameters('baseTemplateUrl'), '/ARM-template/components/')]", 202 | "controlTemplate": { 203 | "True": "controllerAzureRegistry.json", 204 | "False": "controllerCustomRegistry.json" 205 | }, 206 | "actualTemplate": "[concat(variables('baseUrl'), variables('controlTemplate')[string(empty(parameters('registryUrl')))])]" 207 | }, 208 | "resources": [ 209 | { 210 | "type": "Microsoft.Storage/storageAccounts", 211 | "name": "[variables('storageAccountName')]", 212 | "apiVersion": "2017-06-01", 213 | "location": "[parameters('location')]", 214 | "sku": { 215 | "name": "[variables('storageAccountType')]" 216 | }, 217 | "kind": "Storage", 218 | "properties": {} 219 | }, 220 | { 221 | "apiVersion": "2017-04-01", 222 | "type": "Microsoft.Network/networkSecurityGroups", 223 | "name": "[variables('frontEndNSGName')]", 224 | "location": "[parameters('location')]", 225 | "tags": { 226 | "displayName": "NSG - Front End" 227 | }, 228 | "properties": { 229 | "securityRules": [ 230 | { 231 | "name": "ssh-rule", 232 | "properties": { 233 | "description": "Allow SSH", 234 | "protocol": "Tcp", 235 | "sourcePortRange": "*", 236 | "destinationPortRange": "22", 237 | "sourceAddressPrefix": "Internet", 238 | "destinationAddressPrefix": "*", 239 | "access": "Allow", 240 | "priority": 100, 241 | "direction": "Inbound" 242 | } 243 | }, 244 | { 245 | "name": "web-rule", 246 | "properties": { 247 | "description": "Allow WEB", 248 | "protocol": "Tcp", 249 | "sourcePortRange": "*", 250 | "destinationPortRange": "80", 251 | "sourceAddressPrefix": "Internet", 252 | "destinationAddressPrefix": "*", 253 | "access": "Allow", 254 | "priority": 101, 255 | "direction": "Inbound" 256 | } 257 | } 258 | ] 259 | } 260 | }, 261 | { 262 | "apiVersion": "2017-04-01", 263 | "type": "Microsoft.Network/publicIPAddresses", 264 | "name": "[variables('publicIPAddressName')]", 265 | "location": "[parameters('location')]", 266 | "properties": { 267 | "publicIPAllocationMethod": "[variables('publicIPAddressType')]", 268 | "dnsSettings": { 269 | "domainNameLabel": "[variables('controllerDNSNamePrefix')]" 270 | } 271 | } 272 | }, 273 | { 274 | "apiVersion": "2017-04-01", 275 | "type": "Microsoft.Network/virtualNetworks", 276 | "name": "[variables('virtualNetworkName')]", 277 | "location": "[parameters('location')]", 278 | "dependsOn": [ 279 | "[concat('Microsoft.Network/networkSecurityGroups/', variables('frontEndNSGName'))]" 280 | ], 281 | "properties": { 282 | "addressSpace": { 283 | "addressPrefixes": [ 284 | "[variables('addressPrefix')]" 285 | ] 286 | }, 287 | "subnets": [ 288 | { 289 | "name": "[variables('subnetName')]", 290 | "properties": { 291 | "addressPrefix": "[variables('subnetPrefix')]", 292 | "networkSecurityGroup": { 293 | "id": "[resourceId('Microsoft.Network/networkSecurityGroups', variables('frontEndNSGName'))]" 294 | } 295 | } 296 | } 297 | ] 298 | } 299 | }, 300 | { 301 | "apiVersion": "2017-04-01", 302 | "type": "Microsoft.Network/networkInterfaces", 303 | "name": "[variables('nicName')]", 304 | "location": "[parameters('location')]", 305 | "dependsOn": [ 306 | "[resourceId('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]", 307 | "[resourceId('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" 308 | ], 309 | "properties": { 310 | "ipConfigurations": [ 311 | { 312 | "name": "ipconfig1", 313 | "properties": { 314 | "privateIPAllocationMethod": "Dynamic", 315 | "publicIPAddress": { 316 | "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]" 317 | }, 318 | "subnet": { 319 | "id": "[variables('subnetRef')]" 320 | } 321 | } 322 | } 323 | ] 324 | } 325 | }, 326 | { 327 | "apiVersion": "2016-09-01", 328 | "name": "virtualMachine", 329 | "type": "Microsoft.Resources/deployments", 330 | "dependsOn": [ 331 | "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]", 332 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" 333 | ], 334 | "properties": { 335 | "mode": "Incremental", 336 | "templateLink": { 337 | "uri": "[variables('actualTemplate')]", 338 | "contentVersion": "1.0.0.0" 339 | }, 340 | "parameters": { 341 | "dnsNamePrefix": { 342 | "value": "[parameters('dnsNamePrefix')]" 343 | }, 344 | "linuxAdminUsername": { 345 | "value": "[parameters('linuxAdminUsername')]" 346 | }, 347 | "adminPassword": { 348 | "value": "[parameters('adminPassword')]" 349 | }, 350 | "ubuntuOSVersion": { 351 | "value": "[parameters('ubuntuOSVersion')]" 352 | }, 353 | "privateKey": { 354 | "value": "[parameters('privateKey')]" 355 | }, 356 | "location": { 357 | "value": "[parameters('location')]" 358 | }, 359 | "registryUrl": { 360 | "value": "[parameters('registryUrl')]" 361 | }, 362 | "eventHubNamespace": { 363 | "value": "[parameters('eventHubNamespace')]" 364 | }, 365 | "eventHubKeyName": { 366 | "value": "[parameters('eventHubKeyName')]" 367 | }, 368 | "eventHubKey": { 369 | "value": "[parameters('eventHubKey')]" 370 | }, 371 | "eventHubEntityPath": { 372 | "value": "[parameters('eventHubEntityPath')]" 373 | }, 374 | "eventHubPartitionnumber": { 375 | "value": "[parameters('eventHubPartitionnumber')]" 376 | }, 377 | "eventHubThreadWaitSec": { 378 | "value": "[parameters('eventHubThreadWaitSec')]" 379 | }, 380 | "baseTemplateUrl": { 381 | "value": "[parameters('baseTemplateUrl')]" 382 | }, 383 | "storageAccountSku": { 384 | "value": "[parameters('storageAccountSku')]" 385 | }, 386 | "archiveUrl": { 387 | "value": "[parameters('archiveUrl')]" 388 | }, 389 | "directoryName": { 390 | "value": "[parameters('directoryName')]" 391 | }, 392 | "authenticationMode": { 393 | "value": "[parameters('authenticationMode')]" 394 | }, 395 | "azureAdClientId": { 396 | "value": "[parameters('azureAdClientId')]" 397 | }, 398 | "azureAdClientSecret": { 399 | "value": "[parameters('azureAdClientSecret')]" 400 | }, 401 | "tenant": { 402 | "value": "[parameters('tenant')]" 403 | }, 404 | "servicePrincipalClientId": { 405 | "value": "[parameters('servicePrincipalClientId')]" 406 | }, 407 | "servicePrincipalClientSecret": { 408 | "value": "[parameters('servicePrincipalClientSecret')]" 409 | } 410 | } 411 | } 412 | } 413 | ], 414 | "outputs": { 415 | "hostname": { 416 | "type": "string", 417 | "value": "[reference(variables('publicIPAddressName')).dnsSettings.fqdn]" 418 | }, 419 | "sshCommand": { 420 | "type": "string", 421 | "value": "[concat('ssh ', variables('adminUsername'), '@', reference(variables('publicIPAddressName')).dnsSettings.fqdn)]" 422 | } 423 | } 424 | } -------------------------------------------------------------------------------- /ARM-template/components/emptyTemplate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "location": { 6 | "type": "string", 7 | "defaultValue": "[resourceGroup().location]", 8 | "metadata": { 9 | "description": "The location of all components" 10 | } 11 | } 12 | }, 13 | "variables": { 14 | }, 15 | "resources": [ 16 | ], 17 | "outputs": {} 18 | } -------------------------------------------------------------------------------- /ARM-template/components/storageAccount.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "storageAccountSku": { 6 | "type": "string", 7 | "defaultValue": "Standard_LRS", 8 | "allowedValues": [ 9 | "Standard_LRS", 10 | "Premium_LRS" 11 | ], 12 | "metadata": { 13 | "description": "Storage account sku to be used as Elasticsearch data node" 14 | } 15 | }, 16 | "location": { 17 | "type": "string", 18 | "defaultValue": "[resourceGroup().location]", 19 | "metadata": { 20 | "description": "The location of all components" 21 | } 22 | } 23 | }, 24 | "variables": { 25 | "storageAccountType": "[parameters('storageAccountSku')]", 26 | "dataStorageAccountName": "[concat(uniquestring(resourceGroup().id), 'data')]" 27 | }, 28 | "resources": [ 29 | { 30 | "type": "Microsoft.Storage/storageAccounts", 31 | "name": "[variables('dataStorageAccountName')]", 32 | "apiVersion": "2017-06-01", 33 | "location": "[parameters('location')]", 34 | "sku": { 35 | "name": "[variables('storageAccountType')]" 36 | }, 37 | "kind": "Storage", 38 | "properties": {} 39 | } 40 | ], 41 | "outputs": {} 42 | } -------------------------------------------------------------------------------- /ARM-template/param.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "dnsNamePrefix": { 6 | "value": "acs-dns-abx" 7 | }, 8 | "agentCount": { 9 | "value": 1 10 | }, 11 | "agentVMSize": { 12 | "value": "Standard_DS3_v2" 13 | }, 14 | "linuxAdminUsername": { 15 | "value": "yaweiw" 16 | }, 17 | "masterCount": { 18 | "value": 1 19 | }, 20 | "sshRSAPublicKey": { 21 | "value": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCgBTbnVnCvz0mN9aMZTBy90GYk3sBViZ2AQpaL1PHpsrVepcH2ZdouwNDf907asF+vDRG4PTNK9a0lHiEyQPYxH4xIP3xqopIZ1wsC+Jf6l/YpNwNJEUJbOQ6XLPYzl//qt58RAYZj37N1GCaw/ZPxeiwW9mfELTAI+zeVWGsemMdK1j0J/Bl5Ko78IJ5ff8ydX0Nfmy+G0CZ2Xt2n7PXiIjQm7ZHlEqI+cZUZLmyBCroQizfyt4DLaAYO8K6AVIYC6D95nCoH9OpDwlcvJ+4MLup6bjj6EoVUoU8z5Cd7pr2r2818IIe3enSmwwtzisVrm6UD3GM4rwSFEe0mbo7z webcrypto" 22 | }, 23 | "privateKey": { 24 | "value": "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBb0FVMjUxWndyODlKamZXakdVd2N2ZEJtSk43QVZZbWRnRUtXaTlUeDZiSzFYcVhCOW1YYUxzRFEKMy9kTzJyQmZydzBSdUQwelN2V3RKUjRoTWtEMk1SK01TRDk4YXFLU0dkY0xBdmlYK3BmMktUY0RTUkZDV3prT2x5ejJNNWYvCjZyZWZFUUdHWTkremRSZ21zUDJUOFhvc0Z2Wm54QzB3Q1BzM2xWaHJIcGpIU3RZOUNmd1plU3FPL0NDZVgzL01uVjlEWDVzdgpodEFtZGw3ZHArejE0aUkwSnUyUjVSS2lQbkdWR1M1c2dRcTZFSXMzOHJlQXkyZ0dEdkN1Z0ZTR0F1Zy9lWndxQi9UcVE4SlgKTHlmdURDN3FlbTQ0K2hLRlZLRlBNK1FuZTZhOXE5dk5mQ0NIdDNwMHBzTUxjNHJGYTV1bEE5eGpPSzhFaFJIdEptNk84d0lECkFRQUJBb0lCQUJONVB2cTlMcjUrZStXT1FKaDZCajlsUnFEekNMWUFKczR1akZLZENnbVdLWE5JdWNlS0VwakE5MHBpTnRMdgpSdDJ2T2ZwUlhGWWhlcjQ3SWZBVExzTEFvNXBCTzV0Z2lHWWpvTi91TDlTYnZLVzhYNlo3TnJlU1JIL0tSWFgyNS9xQStSY2gKTitFY25xSy8yeW1MMm42M0R2bUdhQTljTG0zUllLeklrMVNvTXNTZlRqTDRja2pqRzB1R3VmcXdkTysvTlZQQXNCOFdleFZ0Ci9rVU1Kbk5Sb1BESmhxbDVYS1NKMnRGandpb0VZRkRXbU0rMTJqVlFIM1RWVTJyWXhndXU5S0NEdS9UeG9jWXhYL24wb1Nhdwo4Ti9ZR0RKRUZLc1BGYjdST1NmTVFNSHNNVnJyV2VsUzNBTHQ0NkhnNktzYTM5N2k3UDVBbDluaDZkYkRyMVVDZ1lFQTBCSC8KeHkxVUVaRjFTVnB0VVlaZEYvTm5SM3lRN3lPMnRQNmRYYkdoME51eXk1V00wMHNCNWlYWUJpSzl6TjhGL0NvVUJkMXFXdk5nCjB2aUNiT01FT2dBRllxajRYK0xreWkva09pdGtudGcwQjYraWZhdkdCUWNwdkVnSkZDdkl6MmNENHMybzdMSUZFSXRvRVhzdwpqdkJXanRINllXM1hXM2ExdFRRUkxkY0NnWUVBeE9Hd1FNdHFONmdiMmpIQndxSzNCeVpvdmkzY1ZzUk9TWjdobDMwa0hQZ3UKVmo0UlhmaEtCc2FuS0J5aWJCOVFIUzFiaGFoR00zOFN0dFlsRUdzVytRbmVGZ1BmaG9Za05qc21zcjRmVExpZmtHczFSUU1WCmFCUjM2czlCMXNvNmhEbis3Rnp5YTBrWTEwUXI2eG1hMjNkaWRSbmxXVjNxQUZEc2NmVnQ3RVVDZ1lFQXBEbkFwMlhJbUFFMgpFSXFXQkNYSUxwSklMeTFMZStTMVhkQWxSd0ZnRmVpQ0M1dmpSaFpLem11S1FQV0UxMElraUM1eUZWcjRpZ2JVb2svUktiNVkKNUtxRmxhU0dQYkJwSW9pQVc4VktLdkc4eDJCQ0lXekY0OHdPamZSUmJpRk00eDZNKzZPVnRCU2lXTGVma1VTcFEvakFhUU9rCjJ1ZUdka09SRWcraGk0c0NnWUVBdkpmY09CVWlxREhXakVxQmFxcG5YT2dFQ2Jqd2lnVDlZVFJhc0x4bExZTG5FQ0hnalAxYwpHK1dMRmJkb050NC81ZE1mQ29kSXgrZC95N2lTUklud3Rab29MVHFFa2Q1SkxkRzNuaHpVNnE4bjJqVjRENldHaWU2aGVFSVUKdFdWK2JFOEp6T3kwSXVVZ2NJU1M5Vi9Qb1U5WTl3ZFpWU1BMRlladkdES2JtMmtDZ1lBK2MvQkhKYm40Y3BYZnUzMU1NUU9yCkZlbCtMRkpnVGp6c0EwQU0vOHhUWGdTMVl6bHZyYVJKYy9JQkpqT0pBaTg4anN3L0JuS2p2VWxESW9UemRGb3pBdk1TcDRGNQpxdGZpN2NHL1RRSFVHdVMvZ0V4UXFkVEh3N3pFTUpaNHo1VXhnV0dRSEp6ajhPVkdYa0ZxOTl3dDRDRkN4SmV3N2NVR0NadHYKMXBwZVZRPT0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0=" 25 | }, 26 | "servicePrincipalClientId": { 27 | "value": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" 28 | }, 29 | "servicePrincipalClientSecret": { 30 | "value": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" 31 | }, 32 | "adminPassword": { 33 | "value": "Password@12345678" 34 | }, 35 | "baseTemplateUrl": { 36 | "value": "https://raw.githubusercontent.com/Microsoft/elk-acs-kubernetes/logstash-eventhub-aztable" 37 | }, 38 | "storageAccountSku": { 39 | "value": "Standard_LRS" 40 | }, 41 | "registryUrl": { 42 | "value": "" 43 | }, 44 | "eventHubNamespace": { 45 | "value": "undefined" 46 | }, 47 | "eventHubKeyName": { 48 | "value": "undefined" 49 | }, 50 | "eventHubKey": { 51 | "value": "undefined" 52 | }, 53 | "eventHubEntityPath": { 54 | "value": "undefined" 55 | }, 56 | "eventHubPartitionnumber": { 57 | "value": "4" 58 | }, 59 | "eventHubThreadWaitSec": { 60 | "value": "10" 61 | }, 62 | "archiveUrl": { 63 | "value": "https://github.com/Microsoft/elk-acs-kubernetes/archive/logstash-eventhub-aztable.zip" 64 | }, 65 | "directoryName": { 66 | "value": "elk-acs-kubernetes-logstash-eventhub-aztable/" 67 | }, 68 | "authenticationMode": { 69 | "value": "BasicAuth" 70 | }, 71 | "azureAdClientId": { 72 | "value": "undefined" 73 | }, 74 | "azureAdClientSecret": { 75 | "value": "undefined" 76 | }, 77 | "tenant": { 78 | "value": "undefined" 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /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 | # Deploy Elastic Stack on Kubernetes in Azure Container Service (ACS) 2 | 3 | 4 | 5 | > This repo is deprecated. 6 | 7 | This repository contains tools and helm charts to help deploy the [Elastck stack](https://www.elastic.co/products) on [Kubernetes](https://kubernetes.io/) in [Azure Container Service (ACS)](https://docs.microsoft.com/azure/container-service/). You can now try this solution template in region: `East US`, `South Central US` and `West Europe` 8 | 9 | ## How the solution works 10 | 11 | * Deploy a Kubernetes cluster on Azure. 12 | * Deploy a Virtual Machine served as the Controller Node to manage and configure Kubernetes cluster on Azure. 13 | * Register Controller Node's FQDN as the entry to Kubernetes dashbord. 14 | * Authentication supported for Kubernetes dashbord: 15 | * Username / Password 16 | * [Azure Active Directory OAuth 2.0](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-code) 17 | * Deploy a Azure Container Registry if no public registry is provided. 18 | * Build docker images for Elastic Stack and push images to the Azure Container Register. If public registry that stores docker images for Elastic Stack is provided, this step is skipped. 19 | * Install Elastic Stack defined as Helm Charts on Kubernetes. 20 | 21 | ## Elastic Stack on Kubernetes Architecture 22 | ![Elastic Stack on Kubernetes Architecture](image/elk-acs-kube-arch.png) 23 | 24 | ## Prerequesites 25 | 26 | * An Azure subscription. If you do not have an Azure subscription, you can sign up for a [Azure Free Trial Subscription](https://azure.microsoft.com/offers/ms-azr-0044p/) 27 | 28 | * Login to your [Azure portal](https://portal.azure.com). 29 | 30 | ## Instructions 31 | 1. Follow tutorial [Create Azure Service Principal using Azure portal](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal) to create an Azure Service Principal and assign it `Contributor` role access to your subscription. 32 | 33 | * Assign application a contributor role to your subscription. The subsciption is the one where you will deploy the Elastic Stack. 34 | > Note: `Application ID`, `Password` and `Tenant ID` will be used in later stages of the deployment. 35 | 36 | 1. Go to Azure Marketplace, find `Elastic Stack on Kubernetes` solution template and click `Create`. 37 | 38 | 1. In `Basics` panel, `Controller Username` and `Controller Password` need to be valid Ubuntu credential and will be used to access Kibana. 39 | > Password must be at least 12 characters long and contain at least one lower case, upper case, digit and special character. 40 | 41 | > `Resource Group` should be a new or an empty one to create your Kubernetes. 42 | 43 | > Note: Due to Azure Container Service - Kubernetes (AKS) in preview isn't available across all regions globally. Deployments in following regions have been verified: `East US`, `South Central US` and `West Europe`. More regions will be supported as AKS enters general availability. Not all **VM sizes** are supported across all regions. You can check product availabilities from [Azure products available by region](https://azure.microsoft.com/en-us/regions/services/) 44 | 45 | 46 | 1. In `Common Settings` panel, provide the following: 47 | * `Dns prefix` - The DNS name prefix of your Kubernetes controller. The `dns prefix` and region location will format your Kubernetes dashboard host name. So the `dns prefix` and `location` pair must be globally unique. 48 | 49 | * `Registry url`- The URL of a public registry that hosts `elasticsearch `, `kibana` and `logstash` docker images. If this field is empty, the solution will automatically create an Azure Container Registry instance. 50 | 51 | > In the following field, you need to enter your Azure Event Hub connect information. If you want the logstash to get logs from log shipper instead of Azure Event hub, keep the `Event hub namespace`/`key name`/`key value` as `undefined`. 52 | 53 | > The Event hub namespace, key name, key value and event hubs can format the event hub's connection string: `Endpoint=sb://.servicebus.windows.net/;SharedAccessKeyName=;SharedAccessKey=;EntityPath=`. The key should be given access with `listen`. 54 | 55 | * `Event hub namespace` - e.g. "myeventhub". 56 | * `Event hub key name` - event hub `SETTINGS` find `Shared access policies` e.g. "RootManageSharedAccessKey". 57 | * `Event hub key value` - SAS policy key value. 58 | * `List of event hubs` - event hub `ENTITIES` find `Event Hubs` and list the event hubs from which you'd pull events e.g. "insights-logs-networksecuritygroupevent,insights-logs-networksecuritygrouprulecounter". Event hubs in the list must be existed and are comma seperated. 59 | 60 | > If you are pulling events out of various event hubs with different partition counts, you are advised to deploy multiple instances of the solution. 61 | 62 | * `Event hub partition count` - partition count of event hubs (all listed event hubs must have the same partition count). 63 | * `Thread wait interval(s)` - logstash event hub plugin thread wait interval in seconds. 64 | 65 | * `Data node storage account sku` - storage account sku used by Elasticsearch data node. 66 | * `Authentication Mode` - authentication mode for accessing Kubernetes dashboard. 67 | * `Basic Authentication` mode uses `Controller Username` and `Controller Password`. 68 | * `Azure Active Directory` mode uses Azure AD service principal for authentication. You need to provide your service principal information which you get at [Step 1](#create-sp): 69 | 70 | * `Azure AD client ID` - [Application ID](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal#get-application-id-and-authentication-key) 71 | * `Azure AD client secret` - [Your generated key](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal#get-application-id-and-authentication-key) 72 | * `Azure AD tenant` - [Tenant ID](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal#get-tenant-id) 73 | 74 | 1. In `Kubernetes Cluster Settings` panel, provide the following: 75 | * `Agent Count` - number of agent nodes of Kubernetes cluster 76 | * `Agent Node Size` 77 | * `Master Count` - number of masters of Kubernetes cluster 78 | 79 | 1. In `Security Settings` panel, provide the following: 80 | 81 | > You can generate the SSH public key/private key pair using [js-keygen](https://microsoft.github.io/elk-acs-kubernetes/) 82 | 83 | * `SSH public key` - ssh public key for controller node to talk to Kubernetes cluster 84 | * `Base64 encoded SSH private key` - base64 encoded ssh private key 85 | 86 | > The `Service principal client ID` and `Service principal client secret` are used to create and manage the Kubernetes cluster, they can be the client id and secret you get from [Step 1](#create-sp). Ensure the Service principal used here has contributor access to your subscription and in the same AAD tenant as your subscription. 87 | 88 | * `Service principal client ID` - Application ID 89 | * `Service principal client secret` - Your generated key 90 | 91 | 92 | 1. Click OK in Summary panel and create the solution. 93 | 94 | > The creation may cost around half an hour. You can continue the next step while the creation. 95 | 96 | 1. If you choose the AAD mode to login your Kubernetes dashboard in [step 4](#aad-login), You need to set the redirect information in Azure Service Principal you created in [step 1](#create-sp). 97 | 98 | 1. Go to your Azure Service Principal: Click `Azure Active Directory` -> `App registrations`, search your Service Princial name and click it. 99 | 100 | 1. Spell out your Kubernetes dashboard host name and note it as ``. The format should be `http://control..cloudapp.azure.com`. 101 | > Both `dns-prefix` and `resource-location` are set in `Basic Panel`. 102 | > `dns-prefix` is specified in `Basic Settings`, `resource-location` is the region where you deploy your Elastic Stack. Deployments in following regions have been verified: `East US`, `South Central US` and `West Europe`. 103 | 104 | 1. Set the Sign-on URL: In the `Settings` page, click `Properties`, set the `Home page URL` to `` you spelled out. Click `Save`. 105 | 106 | 1. Set the redirect URL: In the `Settings` page, click `Reply URLs`, remove the exiting URL, add URL `/callback`. Click `Save`. 107 | 108 | ![Add Azure Service Principal redirect URL](image/elk-acs-kube-aad-redirect.png) 109 | 110 | 1. Grant your Service Principal permissions: In the `Settings` page, click `Required permissions` -> `Windows Azure Active Directory`, tick `Read all users' basic profiles` and `Sign in and read user profile`. Click `Save` in `Enable Access` pane then `Grant Permissions` in `Required permissions` pane. Click `Yes` to confirm the action. 111 | 112 | ![Add Azure Service Principal access](image/elk-acs-kube-aad-access.png) 113 | 114 | ## Acccess your Elastic Stack on Kubernetes 115 | 116 | After the deployment succeeds, you can find the Kubernetes dashboard and kibana/elasticsearch/logstash endpoints 117 | * You can access your Kubernetes dashboard at: 118 | [http://\control.\.cloudapp.azure.com/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard/#!/overview?namespace=elk-cluster-ns](#) 119 | 120 | The namespace is `elk-cluster-ns`. 121 | 122 | * Find kibana/logstash endpoints at `Discovery and Load Balancing` -> `Services` on your Kubernetes dashboard. 123 | 124 | > kibana dashboard's credential is the same as controller you specified in Basic Setting. 125 | 126 | * To manage the Kubernetes cluster, you can use `kubectl` on controllervm. 127 | 128 | > The SSH credential is the same specified in Basic Setting. 129 | 130 | ## How the logs are consumed by your Elastic Stack 131 | 132 | The solution supports two ways to ship logs to Elastic Stack: 133 | * Ingest logs from event hub(s) by logstash input plugin for data from Event Hubs. You need to define index pattern **wad** in Kibana. [Index Patterns](https://www.elastic.co/guide/en/kibana/current/index-patterns.html). To learn more about [Logstash input plugin for data from Event Hubs](https://github.com/Azure/azure-diagnostics-tools/tree/master/Logstash/logstash-input-azureeventhub) 134 | * Log shippers e.g. [Filebeat](https://www.elastic.co/products/beats/filebeat) 135 | 136 | ## Troubleshooting 137 | 138 | * For resource deployment failure, you can find more information from Azure Portal. 139 | * For solution template failure, you can extract logs by ssh to `controllervm`. Deployment log is at `/tmp/output.log`. 140 | 141 | ## Related 142 | 143 | * [Access kubernetes using web UI (dashboard)](https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/) 144 | * [Manage Kubernetes using kubectl](https://kubernetes.io/docs/reference/kubectl/overview/) 145 | * [Scale agent nodes in a Container Service cluster](https://docs.microsoft.com/en-us/azure/container-service/dcos-swarm/container-service-scale) 146 | * [Communication between Kubernetes master and node](https://kubernetes.io/docs/concepts/architecture/master-node-communication/) 147 | * [Ship log to logstash using log shipper filebeat](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-getting-started.html) 148 | * [Azure Event Hubs](https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-features) 149 | * [Stream Azure Diagnostic Logs to an Event Hubs Namespace](https://docs.microsoft.com/en-us/azure/monitoring-and-diagnostics/monitoring-stream-diagnostic-logs-to-event-hubs) 150 | 151 | ## License 152 | 153 | This project is under MIT license. 154 | 155 | `config/openidc.lua` is derived from [https://github.com/pingidentity/lua-resty-openidc](https://github.com/pingidentity/lua-resty-openidc) with some modifications to satisfy requirements and this file (`config/openidc.lua`) is under Apache 2.0 license. 156 | 157 | # Contributing 158 | 159 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 160 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 161 | the rights to use your contribution. For details, visit https://cla.microsoft.com. 162 | 163 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 164 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 165 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 166 | -------------------------------------------------------------------------------- /config/nginx-basic.conf: -------------------------------------------------------------------------------- 1 | events { 2 | worker_connections 1024; 3 | } 4 | 5 | http { 6 | server { 7 | listen 80 default_server; 8 | listen [::]:80 default_server; 9 | 10 | server_name _; 11 | 12 | location / { 13 | proxy_pass http://localhost:8080; 14 | auth_basic "Restrict Access"; 15 | auth_basic_user_file /usr/local/openresty/nginx/conf/.htpasswd; 16 | } 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /config/nginx-openid.conf: -------------------------------------------------------------------------------- 1 | events { 2 | worker_connections 1024; 3 | } 4 | 5 | http { 6 | 7 | lua_package_path '~/lua/?.lua;;'; 8 | 9 | resolver 8.8.8.8; 10 | 11 | lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; 12 | lua_ssl_verify_depth 5; 13 | 14 | # cache for discovery metadata documents 15 | lua_shared_dict discovery 1m; 16 | 17 | # NB: if you have "lua_code_cache off;", use: 18 | # set $session_secret xxxxxxxxxxxxxxxxxxx; 19 | # see: https://github.com/bungle/lua-resty-session#notes-about-turning-lua-code-cache-off 20 | 21 | server { 22 | listen 80; 23 | 24 | location / { 25 | 26 | access_by_lua ' 27 | 28 | local opts = { 29 | -- the full redirect URI must be protected by this script and becomes: 30 | -- ngx.var.scheme.."://"..ngx.var.http_host..opts.redirect_uri_path 31 | -- unless the scheme is overridden using opts.redirect_uri_scheme or an X-Forwarded-Proto header in the incoming request 32 | redirect_uri_path = "/callback", 33 | discovery = "https://login.microsoftonline.com/${TENANT}/.well-known/openid-configuration", 34 | client_id = "${CLIENT_ID}", 35 | client_secret = "${CLIENT_SECRET}", 36 | -- default iat_slack is 120 which is insufficient. 37 | iat_slack = 600, 38 | -- if graph API will be called, uncomment next line. 39 | -- authorization_params = { resource="https://graph.windows.net" }, 40 | -- scope = "openid email profile", 41 | -- Refresh the user id_token after 900 seconds without requiring re-authentication 42 | -- refresh_session_interval = 900, 43 | -- redirect_uri_scheme = "https", 44 | -- logout_path = "/logout", 45 | -- redirect_after_logout_uri = "/", 46 | -- redirect_after_logout_with_id_token_hint = true, 47 | -- token_endpoint_auth_method = ["client_secret_basic"|"client_secret_post"], 48 | -- ssl_verify = "no" 49 | -- access_token_expires_in = 3600 50 | -- Default lifetime in seconds of the access_token if no expires_in attribute is present in the token 51 | -- endpoint response. 52 | -- This plugin will silently renew the access_token once it is expired if refreshToken scope is present. 53 | -- access_token_expires_leeway = 0 54 | -- Expiration leeway for access_token renewal. 55 | -- If this is set, renewal will happen access_token_expires_leeway seconds before the token expiration. 56 | -- This avoids errors in case the access_token just expires when arriving to the OAuth Resoource Server. 57 | -- force_reauthorize = false 58 | -- when force_reauthorize is set to true the authorization flow will be executed even if a token has been cached already 59 | } 60 | 61 | -- call authenticate for OpenID Connect user authentication 62 | local res, err = require("resty.openidc").authenticate(opts) 63 | 64 | if err then 65 | ngx.status = 500 66 | ngx.say(err) 67 | ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) 68 | end 69 | 70 | -- at this point res is a Lua table with 3 keys: 71 | -- id_token : a Lua table with the claims from the id_token (required) 72 | -- access_token: the access token (optional) 73 | -- user : a Lua table with the claims returned from the user info endpoint (optional) 74 | 75 | --if res.id_token.hd ~= "pingidentity.com" then 76 | -- ngx.exit(ngx.HTTP_FORBIDDEN) 77 | --end 78 | 79 | --if res.user.email ~= "hans.zandbelt@zmartzone.eu" then 80 | -- ngx.exit(ngx.HTTP_FORBIDDEN) 81 | --end 82 | 83 | -- set headers with user info: this will overwrite any existing headers 84 | -- but also scrub(!) them in case no value is provided in the token 85 | ngx.req.set_header("X-USER", res.id_token.sub) 86 | '; 87 | 88 | proxy_pass http://localhost:8080; 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /config/openidc.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | 19 | *************************************************************************** 20 | Copyright (C) 2015-2017 Ping Identity Corporation 21 | All rights reserved. 22 | 23 | For further information please contact: 24 | 25 | Ping Identity Corporation 26 | 1099 18th St Suite 2950 27 | Denver, CO 80202 28 | 303.468.2900 29 | http://www.pingidentity.com 30 | 31 | DISCLAIMER OF WARRANTIES: 32 | 33 | THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT 34 | ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, 35 | WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, 36 | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY 37 | WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE 38 | USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET 39 | YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE 40 | WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR 41 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 42 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF 43 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 44 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 45 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 46 | 47 | @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu 48 | --]] 49 | 50 | local require = require 51 | local cjson = require "cjson" 52 | local http = require "resty.http" 53 | local string = string 54 | local ipairs = ipairs 55 | local pairs = pairs 56 | local type = type 57 | local ngx = ngx 58 | 59 | local openidc = { 60 | _VERSION = "1.4.0" 61 | } 62 | openidc.__index = openidc 63 | 64 | -- set value in server-wide cache if available 65 | local function openidc_cache_set(type, key, value, exp) 66 | local dict = ngx.shared[type] 67 | if dict then 68 | local success, err, forcible = dict:set(key, value, exp) 69 | ngx.log(ngx.DEBUG, "cache set: success=", success, " err=", err, " forcible=", forcible) 70 | end 71 | end 72 | 73 | -- retrieve value from server-wide cache if available 74 | local function openidc_cache_get(type, key) 75 | local dict = ngx.shared[type] 76 | local value 77 | local flags 78 | if dict then 79 | value, flags = dict:get(key) 80 | if value then ngx.log(ngx.DEBUG, "cache hit: type=", type, " key=", key) end 81 | end 82 | return value 83 | end 84 | 85 | -- validate the contents of and id_token 86 | local function openidc_validate_id_token(opts, id_token, nonce) 87 | 88 | -- check issuer 89 | if opts.discovery.issuer ~= id_token.iss then 90 | ngx.log(ngx.ERR, "issuer \"", id_token.iss, " in id_token is not equal to the issuer from the discovery document \"", opts.discovery.issuer, "\"") 91 | return false 92 | end 93 | 94 | -- check nonce 95 | if nonce and nonce ~= id_token.nonce then 96 | ngx.log(ngx.ERR, "nonce \"", id_token.nonce, " in id_token is not equal to the nonce that was sent in the request \"", nonce, "\"") 97 | return false 98 | end 99 | 100 | -- check issued-at timestamp 101 | if not id_token.iat then 102 | ngx.log(ngx.ERR, "no \"iat\" claim found in id_token") 103 | return false 104 | end 105 | 106 | local slack=opts.iat_slack and opts.iat_slack or 120 107 | if id_token.iat < (ngx.time() - slack) then 108 | ngx.log(ngx.ERR, "token is not valid yet: id_token.iat=", id_token.iat, ", ngx.time()=", ngx.time()) 109 | return false 110 | end 111 | 112 | -- check expiry timestamp 113 | if id_token.exp < ngx.time() then 114 | ngx.log(ngx.ERR, "token expired: id_token.exp=", id_token.exp, ", ngx.time()=", ngx.time()) 115 | return false 116 | end 117 | 118 | -- check audience (array or string) 119 | if (type(id_token.aud) == "table") then 120 | for key, value in pairs(id_token.aud) do 121 | if value == opts.client_id then 122 | return true 123 | end 124 | end 125 | ngx.log(ngx.ERR, "no match found token audience array: client_id=", opts.client_id ) 126 | return false 127 | elseif (type(id_token.aud) == "string") then 128 | if id_token.aud ~= opts.client_id then 129 | ngx.log(ngx.ERR, "token audience does not match: id_token.aud=", id_token.aud, ", client_id=", opts.client_id ) 130 | return false 131 | end 132 | end 133 | return true 134 | end 135 | 136 | -- assemble the redirect_uri 137 | local function openidc_get_redirect_uri(opts) 138 | local scheme = opts.redirect_uri_scheme or ngx.req.get_headers()['X-Forwarded-Proto'] or ngx.var.scheme 139 | if not ngx.var.http_host then 140 | -- possibly HTTP 1.0 and no Host header 141 | ngx.exit(ngx.HTTP_BAD_REQUEST) 142 | end 143 | return scheme.."://"..ngx.var.http_host ..opts.redirect_uri_path 144 | end 145 | 146 | -- perform base64url decoding 147 | local function openidc_base64_url_decode(input) 148 | local reminder = #input % 4 149 | if reminder > 0 then 150 | local padlen = 4 - reminder 151 | input = input .. string.rep('=', padlen) 152 | end 153 | input = input:gsub('-','+'):gsub('_','/') 154 | return ngx.decode_base64(input) 155 | end 156 | 157 | -- perform base64url encoding 158 | local function openidc_base64_url_encode(input) 159 | input = ngx.encode_base64(input) 160 | return input:gsub('+','-'):gsub('/','_'):gsub('=','') 161 | end 162 | 163 | -- send the browser of to the OP's authorization endpoint 164 | local function openidc_authorize(opts, session, target_url) 165 | local resty_random = require "resty.random" 166 | local resty_string = require "resty.string" 167 | 168 | -- generate state and nonce 169 | local state = resty_string.to_hex(resty_random.bytes(16)) 170 | local nonce = resty_string.to_hex(resty_random.bytes(16)) 171 | 172 | -- assemble the parameters to the authentication request 173 | local params = { 174 | client_id=opts.client_id, 175 | response_type="code", 176 | scope=opts.scope and opts.scope or "openid email profile", 177 | redirect_uri=openidc_get_redirect_uri(opts), 178 | state=state, 179 | nonce=nonce, 180 | prompt=opts.prompt and opts.prompt or "" 181 | } 182 | 183 | -- merge any provided extra parameters 184 | if opts.authorization_params then 185 | for k,v in pairs(opts.authorization_params) do params[k] = v end 186 | end 187 | 188 | -- store state in the session 189 | session:start() 190 | session.data.original_url = target_url 191 | session.data.state = state 192 | session.data.nonce = nonce 193 | session.data.last_authenticated = ngx.time() 194 | session:save() 195 | 196 | -- redirect to the /authorization endpoint 197 | return ngx.redirect(opts.discovery.authorization_endpoint.."?"..ngx.encode_args(params)) 198 | end 199 | 200 | -- parse the JSON result from a call to the OP 201 | local function openidc_parse_json_response(response) 202 | 203 | local err 204 | local res 205 | 206 | -- check the response from the OP 207 | if response.status ~= 200 then 208 | err = "response indicates failure, status="..response.status..", body="..response.body 209 | else 210 | -- decode the response and extract the JSON object 211 | res = cjson.decode(response.body) 212 | 213 | if not res then 214 | err = "JSON decoding failed" 215 | end 216 | end 217 | 218 | return res, err 219 | end 220 | 221 | -- make a call to the token endpoint 222 | local function openidc_call_token_endpoint(opts, endpoint, body, auth) 223 | 224 | local headers = { 225 | ["Content-Type"] = "application/x-www-form-urlencoded" 226 | } 227 | 228 | if auth then 229 | if auth == "client_secret_basic" then 230 | headers.Authorization = "Basic "..ngx.encode_base64( opts.client_id..":"..opts.client_secret) 231 | ngx.log(ngx.DEBUG,"client_secret_basic: authorization header '"..headers.Authorization.."'") 232 | end 233 | if auth == "client_secret_post" then 234 | body.client_id=opts.client_id 235 | body.client_secret=opts.client_secret 236 | ngx.log(ngx.DEBUG, "client_secret_post: client_id and client_secret being sent in POST body") 237 | end 238 | end 239 | 240 | ngx.log(ngx.DEBUG, "request body for token endpoint call: ", ngx.encode_args(body)) 241 | 242 | local httpc = http.new() 243 | local res, err = httpc:request_uri(endpoint, { 244 | method = "POST", 245 | body = ngx.encode_args(body), 246 | headers = headers, 247 | ssl_verify = (opts.ssl_verify ~= "no") 248 | }) 249 | if not res then 250 | err = "accessing token endpoint ("..endpoint..") failed: "..err 251 | ngx.log(ngx.ERR, err) 252 | return nil, err 253 | end 254 | 255 | ngx.log(ngx.DEBUG, "token endpoint response: ", res.body) 256 | 257 | return openidc_parse_json_response(res); 258 | end 259 | 260 | -- make a call to the userinfo endpoint 261 | local function openidc_call_userinfo_endpoint(opts, access_token) 262 | if not opts.discovery.userinfo_endpoint then 263 | ngx.log(ngx.DEBUG, "no userinfo endpoint supplied") 264 | return nil, nil 265 | end 266 | 267 | local httpc = http.new() 268 | local res, err = httpc:request_uri(opts.discovery.userinfo_endpoint, { 269 | headers = { 270 | ["Authorization"] = "Bearer "..access_token, 271 | } 272 | }) 273 | if not res then 274 | err = "accessing userinfo endpoint ("..opts.discovery.userinfo_endpoint..") failed: "..err 275 | ngx.log(ngx.ERR, err) 276 | return nil, err 277 | end 278 | 279 | ngx.log(ngx.DEBUG, "userinfo response: ", res.body) 280 | 281 | -- parse the response from the user info endpoint 282 | return openidc_parse_json_response(res) 283 | end 284 | 285 | -- computes access_token expires_in value (in seconds) 286 | local function openidc_access_token_expires_in(opts, expires_in) 287 | return (expires_in or opts.access_token_expires_in or 3600) - 1 - (opts.access_token_expires_leeway or 0) 288 | end 289 | 290 | -- handle a "code" authorization response from the OP 291 | local function openidc_authorization_response(opts, session) 292 | local args = ngx.req.get_uri_args() 293 | local err 294 | 295 | if not args.code or not args.state then 296 | err = "unhandled request to the redirect_uri: "..ngx.var.request_uri 297 | ngx.log(ngx.ERR, err) 298 | return nil, err, session.data.original_url, session 299 | end 300 | 301 | -- check that the state returned in the response against the session; prevents CSRF 302 | if args.state ~= session.data.state then 303 | err = "state from argument: "..(args.state and args.state or "nil").." does not match state restored from session: "..(session.data.state and session.data.state or "nil") 304 | ngx.log(ngx.ERR, err) 305 | return nil, err, session.data.original_url, session 306 | end 307 | 308 | -- check the iss if returned from the OP 309 | if args.iss and args.iss ~= opts.discovery.issuer then 310 | err = "iss from argument: "..args.iss.." does not match expected issuer: "..opts.discovery.issuer 311 | ngx.log(ngx.ERR, err) 312 | return nil, err, session.data.original_url, session 313 | end 314 | 315 | -- check the client_id if returned from the OP 316 | if args.client_id and args.client_id ~= opts.client_id then 317 | err = "client_id from argument: "..args.client_id.." does not match expected client_id: "..opts.client_id 318 | ngx.log(ngx.ERR, err) 319 | return nil, err, session.data.original_url, session 320 | end 321 | 322 | -- assemble the parameters to the token endpoint 323 | local body = { 324 | grant_type="authorization_code", 325 | code=args.code, 326 | redirect_uri=openidc_get_redirect_uri(opts), 327 | state = session.data.state 328 | } 329 | 330 | local current_time = ngx.time() 331 | -- make the call to the token endpoint 332 | local json, err = openidc_call_token_endpoint(opts, opts.discovery.token_endpoint, body, opts.token_endpoint_auth_method) 333 | if err then 334 | return nil, err, session.data.original_url, session 335 | end 336 | 337 | -- process the token endpoint response with the id_token and access_token 338 | local enc_hdr, enc_pay, enc_sign = string.match(json.id_token, '^(.+)%.(.+)%.(.+)$') 339 | local jwt = openidc_base64_url_decode(enc_pay) 340 | local id_token = cjson.decode(jwt) 341 | 342 | -- validate the id_token contents 343 | if openidc_validate_id_token(opts, id_token, session.data.nonce) == false then 344 | err = "id_token validation failed" 345 | return nil, err, session.data.original_url, session 346 | end 347 | 348 | -- call the user info endpoint 349 | -- TODO: should this error be checked? 350 | local user, err = openidc_call_userinfo_endpoint(opts, json.access_token) 351 | 352 | session:start() 353 | session.data.user = user 354 | session.data.id_token = id_token 355 | session.data.enc_id_token = json.id_token 356 | session.data.access_token = json.access_token 357 | session.data.access_token_expiration = current_time 358 | + openidc_access_token_expires_in(opts, json.expires_in) 359 | if json.refresh_token ~= nil then 360 | session.data.refresh_token = json.refresh_token 361 | end 362 | 363 | -- save the session with the obtained id_token 364 | session:save() 365 | 366 | -- redirect to the URL that was accessed originally 367 | ngx.redirect(session.data.original_url) 368 | return nil, nil, session.data.original_url, session 369 | 370 | end 371 | 372 | -- get the Discovery metadata from the specified URL 373 | local function openidc_discover(url, ssl_verify) 374 | ngx.log(ngx.DEBUG, "openidc_discover: URL is: "..url) 375 | 376 | local json, err 377 | local v = openidc_cache_get("discovery", url) 378 | if not v then 379 | 380 | ngx.log(ngx.DEBUG, "discovery data not in cache, making call to discovery endpoint") 381 | -- make the call to the discovery endpoint 382 | local httpc = http.new() 383 | local res, error = httpc:request_uri(url, { 384 | ssl_verify = (ssl_verify ~= "no") 385 | }) 386 | if not res then 387 | err = "accessing discovery url ("..url..") failed: "..error 388 | ngx.log(ngx.ERR, err) 389 | else 390 | ngx.log(ngx.DEBUG, "response data: "..res.body) 391 | json, err = openidc_parse_json_response(res) 392 | if json then 393 | openidc_cache_set("discovery", url, cjson.encode(json), 24 * 60 * 60) 394 | else 395 | err = "could not decode JSON from Discovery data" 396 | end 397 | end 398 | 399 | else 400 | json = cjson.decode(v) 401 | end 402 | 403 | return json, err 404 | end 405 | 406 | local function openidc_jwks(url, ssl_verify) 407 | ngx.log(ngx.DEBUG, "openidc_jwks: URL is: "..url) 408 | 409 | local json, err 410 | local v = openidc_cache_get("jwks", url) 411 | if not v then 412 | 413 | ngx.log(ngx.DEBUG, "JWKS data not in cache. Making call to jwks endpoint") 414 | -- make the call to the jwks endpoint 415 | local httpc = http.new() 416 | local res, error = httpc:request_uri(url, { 417 | ssl_verify = (ssl_verify ~= "no") 418 | }) 419 | if not res then 420 | err = "accessing jwks url ("..url..") failed: "..error 421 | ngx.log(ngx.ERR, err) 422 | else 423 | ngx.log(ngx.DEBUG, "response data: "..res.body) 424 | json, err = openidc_parse_json_response(res) 425 | if json then 426 | openidc_cache_set("jwks", url, cjson.encode(json), 24 * 60 * 60) 427 | end 428 | end 429 | 430 | else 431 | json = cjson.decode(v) 432 | end 433 | 434 | return json, err 435 | end 436 | 437 | local function split_by_chunk(text, chunkSize) 438 | local s = {} 439 | for i=1, #text, chunkSize do 440 | s[#s+1] = text:sub(i,i+chunkSize - 1) 441 | end 442 | return s 443 | end 444 | 445 | local function get_jwk (keys, kid) 446 | for _, value in pairs(keys) do 447 | if value.kid == kid then 448 | return value 449 | end 450 | end 451 | 452 | return nil 453 | end 454 | 455 | local function pem_from_jwk (opts, kid) 456 | local cache_id = opts.discovery.jwks_uri .. '#' .. kid 457 | local v = openidc_cache_get("jwks", cache_id) 458 | 459 | if v then 460 | return v 461 | end 462 | 463 | local jwks, err = openidc_jwks(opts.discovery.jwks_uri, opts.ssl_verify) 464 | if err then 465 | return nil, err 466 | end 467 | 468 | local x5c = get_jwk(jwks.keys, kid).x5c 469 | -- TODO check x5c length 470 | local chunks = split_by_chunk(ngx.encode_base64(openidc_base64_url_decode(x5c[1])), 64) 471 | local pem = "-----BEGIN CERTIFICATE-----\n" .. table.concat(chunks, "\n") .. "\n-----END CERTIFICATE-----" 472 | openidc_cache_set("jwks", cache_id, pem, 24 * 60 * 60) 473 | return pem 474 | end 475 | 476 | local openidc_transparent_pixel = "\137\080\078\071\013\010\026\010\000\000\000\013\073\072\068\082" .. 477 | "\000\000\000\001\000\000\000\001\008\004\000\000\000\181\028\012" .. 478 | "\002\000\000\000\011\073\068\065\084\120\156\099\250\207\000\000" .. 479 | "\002\007\001\002\154\028\049\113\000\000\000\000\073\069\078\068" .. 480 | "\174\066\096\130" 481 | 482 | -- handle logout 483 | local function openidc_logout(opts, session) 484 | local session_token = session.data.enc_id_token 485 | session:destroy() 486 | local headers = ngx.req.get_headers() 487 | local header = headers['Accept'] 488 | if header and header:find("image/png") then 489 | ngx.header["Cache-Control"] = "no-cache, no-store" 490 | ngx.header["Pragma"] = "no-cache" 491 | ngx.header["P3P"] = "CAO PSA OUR" 492 | ngx.header["Expires"] = "0" 493 | ngx.header["X-Frame-Options"] = "DENY" 494 | ngx.header.content_type = "image/png" 495 | ngx.print(openidc_transparent_pixel) 496 | ngx.exit(ngx.OK) 497 | return 498 | elseif opts.redirect_after_logout_uri and opts.redirect_after_logout_with_id_token_hint then 499 | return ngx.redirect(opts.redirect_after_logout_uri.."&id_token_hint="..session_token) 500 | elseif opts.redirect_after_logout_uri then 501 | return ngx.redirect(opts.redirect_after_logout_uri) 502 | elseif opts.discovery.end_session_endpoint then 503 | return ngx.redirect(opts.discovery.end_session_endpoint) 504 | elseif opts.discovery.ping_end_session_endpoint then 505 | return ngx.redirect(opts.discovery.ping_end_session_endpoint) 506 | end 507 | 508 | ngx.header.content_type = "text/html" 509 | ngx.say("Logged Out") 510 | ngx.exit(ngx.OK) 511 | end 512 | 513 | -- get the token endpoint authentication method 514 | local function openidc_get_token_auth_method(opts) 515 | 516 | local result 517 | if opts.discovery.token_endpoint_auth_methods_supported ~= nil then 518 | -- if set check to make sure the discovery data includes the selected client auth method 519 | if opts.token_endpoint_auth_method ~= nil then 520 | for index, value in ipairs (opts.discovery.token_endpoint_auth_methods_supported) do 521 | ngx.log(ngx.DEBUG, index.." => "..value) 522 | if value == opts.token_endpoint_auth_method then 523 | ngx.log(ngx.DEBUG, "configured value for token_endpoint_auth_method ("..opts.token_endpoint_auth_method..") found in token_endpoint_auth_methods_supported in metadata") 524 | result = opts.token_endpoint_auth_method 525 | break 526 | end 527 | end 528 | if result == nil then 529 | ngx.log(ngx.ERR, "configured value for token_endpoint_auth_method ("..opts.token_endpoint_auth_method..") NOT found in token_endpoint_auth_methods_supported in metadata") 530 | return nil 531 | end 532 | else 533 | result = opts.discovery.token_endpoint_auth_methods_supported[1] 534 | ngx.log(ngx.DEBUG, "no configuration setting for option so select the first method specified by the OP: "..result) 535 | end 536 | else 537 | result = opts.token_endpoint_auth_method 538 | end 539 | 540 | -- set a sane default if auto-configuration failed 541 | if result == nil then 542 | result = "client_secret_basic" 543 | end 544 | 545 | ngx.log(ngx.DEBUG, "token_endpoint_auth_method result set to "..result) 546 | 547 | return result 548 | end 549 | 550 | -- returns a valid access_token (eventually refreshing the token) 551 | local function openidc_access_token(opts, session) 552 | 553 | local err 554 | 555 | if session.data.access_token == nil then 556 | return nil, err 557 | end 558 | local current_time = ngx.time() 559 | if current_time < session.data.access_token_expiration then 560 | return session.data.access_token, err 561 | end 562 | if session.data.refresh_token == nil then 563 | return nil, err 564 | end 565 | 566 | ngx.log(ngx.DEBUG, "refreshing expired access_token: ", session.data.access_token, " with: ", session.data.refresh_token) 567 | 568 | -- retrieve token endpoint URL from discovery endpoint if necessary 569 | if type(opts.discovery) == "string" then 570 | opts.discovery, err = openidc_discover(opts.discovery, opts.ssl_verify) 571 | if err then 572 | return nil, err 573 | end 574 | end 575 | 576 | -- set the authentication method for the token endpoint 577 | opts.token_endpoint_auth_method = openidc_get_token_auth_method(opts) 578 | -- assemble the parameters to the token endpoint 579 | local body = { 580 | grant_type="refresh_token", 581 | refresh_token=session.data.refresh_token, 582 | scope=opts.scope and opts.scope or "openid email profile" 583 | } 584 | 585 | local json, err = openidc_call_token_endpoint(opts, opts.discovery.token_endpoint, body, opts.token_endpoint_auth_method) 586 | if err then 587 | return nil, err 588 | end 589 | ngx.log(ngx.DEBUG, "access_token refreshed: ", json.access_token, " updated refresh_token: ", json.refresh_token) 590 | 591 | session:start() 592 | session.data.access_token = json.access_token 593 | session.data.access_token_expiration = current_time + openidc_access_token_expires_in(opts, json.expires_in) 594 | if json.refresh_token ~= nil then 595 | session.data.refresh_token = json.refresh_token 596 | end 597 | 598 | -- save the session with the new access_token and optionally the new refresh_token 599 | session:save() 600 | 601 | return session.data.access_token, err 602 | 603 | end 604 | 605 | -- main routine for OpenID Connect user authentication 606 | function openidc.authenticate(opts, target_url, unauth_action, session_opts) 607 | 608 | local err 609 | 610 | local session = require("resty.session").open(session_opts) 611 | 612 | local target_url = target_url or ngx.var.request_uri 613 | 614 | local access_token 615 | 616 | if type(opts.discovery) == "string" then 617 | --if session.data.discovery then 618 | -- opts.discovery = session.data.discovery 619 | --else 620 | -- session.data.discovery = opts.discovery 621 | --end 622 | opts.discovery, err = openidc_discover(opts.discovery, opts.ssl_verify) 623 | if err then 624 | return nil, err, target_url, session 625 | end 626 | end 627 | 628 | -- set the authentication method for the token endpoint 629 | opts.token_endpoint_auth_method = openidc_get_token_auth_method(opts) 630 | 631 | -- see if this is a request to the redirect_uri i.e. an authorization response 632 | local path = target_url:match("(.-)%?") or target_url 633 | if path == opts.redirect_uri_path then 634 | if not session.present then 635 | err = "request to the redirect_uri_path but there's no session state found" 636 | ngx.log(ngx.ERR, err) 637 | return nil, err, target_url, session 638 | end 639 | return openidc_authorization_response(opts, session) 640 | end 641 | 642 | -- see if this is a request to logout 643 | if path == (opts.logout_path and opts.logout_path or "/logout") then 644 | openidc_logout(opts, session) 645 | return nil, nil, target_url, session 646 | end 647 | 648 | -- if we have no id_token then redirect to the OP for authentication 649 | if not session.present or not session.data.id_token or opts.force_reauthorize then 650 | if unauth_action == "pass" then 651 | return 652 | nil, 653 | err, 654 | target_url, 655 | session 656 | end 657 | openidc_authorize(opts, session, target_url) 658 | return nil, nil, target_url, session 659 | end 660 | 661 | -- silently reauthenticate if necessary (mainly used for session refresh/getting updated id_token data) 662 | if opts.refresh_session_interval ~= nil then 663 | if session.data.last_authenticated == nil or (session.data.last_authenticated+opts.refresh_session_interval) < ngx.time() then 664 | opts.prompt = "none" 665 | openidc_authorize(opts, session, target_url) 666 | return nil, nil, target_url, session 667 | end 668 | end 669 | 670 | -- refresh access_token if necessary 671 | access_token, err = openidc_access_token(opts, session) 672 | if err then 673 | return nil, err, target_url, session 674 | end 675 | 676 | -- log id_token contents 677 | ngx.log(ngx.DEBUG, "id_token=", cjson.encode(session.data.id_token)) 678 | 679 | -- return the id_token to the caller Lua script for access control purposes 680 | return 681 | { 682 | id_token=session.data.id_token, 683 | access_token=access_token, 684 | user=session.data.user 685 | }, 686 | err, 687 | target_url, 688 | session 689 | end 690 | 691 | -- get a valid access_token (eventually refreshing the token), or nil if there's no valid access_token 692 | function openidc.access_token(opts, session_opts) 693 | 694 | local session = require("resty.session").open(session_opts) 695 | 696 | return openidc_access_token(opts, session) 697 | 698 | end 699 | 700 | -- get an OAuth 2.0 bearer access token from the HTTP request 701 | local function openidc_get_bearer_access_token(opts) 702 | 703 | local err 704 | 705 | -- get the access token from the Authorization header 706 | local headers = ngx.req.get_headers() 707 | local header = headers['Authorization'] 708 | 709 | if header == nil or header:find(" ") == nil then 710 | err = "no Authorization header found" 711 | ngx.log(ngx.ERR, err) 712 | return nil, err 713 | end 714 | 715 | local divider = header:find(' ') 716 | if string.lower(header:sub(0, divider-1)) ~= string.lower("Bearer") then 717 | err = "no Bearer authorization header value found" 718 | ngx.log(ngx.ERR, err) 719 | return nil, err 720 | end 721 | 722 | local access_token = header:sub(divider+1) 723 | if access_token == nil then 724 | err = "no Bearer access token value found" 725 | ngx.log(ngx.ERR, err) 726 | return nil, err 727 | end 728 | 729 | return access_token, err 730 | end 731 | 732 | -- main routine for OAuth 2.0 token introspection 733 | function openidc.introspect(opts) 734 | 735 | -- get the access token from the request 736 | local access_token, err = openidc_get_bearer_access_token(opts) 737 | if access_token == nil then 738 | return nil, err 739 | end 740 | 741 | -- see if we've previously cached the introspection result for this access token 742 | local json 743 | local v = openidc_cache_get("introspection", access_token) 744 | if not v then 745 | 746 | -- assemble the parameters to the introspection (token) endpoint 747 | local token_param_name = opts.introspection_token_param_name and opts.introspection_token_param_name or "token" 748 | 749 | local body = {} 750 | 751 | body[token_param_name]= access_token 752 | 753 | if opts.client_id then 754 | body.client_id=opts.client_id 755 | end 756 | if opts.client_secret then 757 | body.client_secret=opts.client_secret 758 | end 759 | 760 | -- merge any provided extra parameters 761 | if opts.introspection_params then 762 | for k,v in pairs(opts.introspection_params) do body[k] = v end 763 | end 764 | 765 | -- call the introspection endpoint 766 | json, err = openidc_call_token_endpoint(opts, opts.introspection_endpoint, body, nil) 767 | 768 | -- cache the results 769 | if json then 770 | local expiry_claim = opts.introspection_expiry_claim or "exp" 771 | if json.active or json[expiry_claim] then 772 | local ttl = json[expiry_claim] 773 | if expiry_claim == "exp" then --https://tools.ietf.org/html/rfc7662#section-2.2 774 | ttl = ttl - ngx.time() 775 | end 776 | ngx.log(ngx.DEBUG, "cache token ttl: "..ttl) 777 | openidc_cache_set("introspection", access_token, cjson.encode(json), ttl) 778 | else 779 | err = "invalid token" 780 | end 781 | end 782 | 783 | else 784 | json = cjson.decode(v) 785 | end 786 | 787 | return json, err 788 | end 789 | 790 | -- main routine for OAuth 2.0 JWT token validation 791 | -- optional args are claim specs, see jwt-validators in resty.jwt 792 | function openidc.jwt_verify(access_token, opts, ...) 793 | local err 794 | local json 795 | 796 | -- see if we've previously cached the validation result for this access token 797 | local v = openidc_cache_get("introspection", access_token) 798 | if not v then 799 | 800 | -- do the verification first time 801 | local jwt = require "resty.jwt" 802 | 803 | -- No secret given try getting it from the jwks endpoint 804 | if not opts.secret and opts.discovery then 805 | ngx.log(ngx.DEBUG, "bearer_jwt_verify using discovery.") 806 | if type(opts.discovery) == "string" then 807 | opts.discovery, err = openidc_discover(opts.discovery, opts.ssl_verify) 808 | if err then 809 | return nil, err 810 | end 811 | end 812 | 813 | -- We decode the token twice, could be saved 814 | local jwt_obj = jwt:load_jwt(access_token, nil) 815 | 816 | if not jwt_obj.valid then 817 | return nil, "invalid jwt" 818 | end 819 | 820 | opts.secret, err = pem_from_jwk(opts, jwt_obj.header.kid) 821 | 822 | if opts.secret == nil then 823 | return nil, err 824 | end 825 | end 826 | 827 | json = jwt:verify(opts.secret, access_token, ...) 828 | 829 | ngx.log(ngx.DEBUG, "jwt: ", cjson.encode(json)) 830 | 831 | -- cache the results 832 | if json and json.valid == true and json.verified == true then 833 | json = json.payload 834 | openidc_cache_set("introspection", access_token, cjson.encode(json), json.exp - ngx.time()) 835 | else 836 | err = "invalid token: ".. json.reason 837 | end 838 | 839 | else 840 | -- decode from the cache 841 | json = cjson.decode(v) 842 | end 843 | 844 | local slack=opts.iat_slack and opts.iat_slack or 120 845 | -- check the token expiry 846 | if json then 847 | if json.exp and json.exp + slack < ngx.time() then 848 | ngx.log(ngx.ERR, "token expired: json.exp=", json.exp, ", ngx.time()=", ngx.time()) 849 | err = "JWT expired" 850 | end 851 | end 852 | 853 | return json, err 854 | end 855 | 856 | function openidc.bearer_jwt_verify(opts, ...) 857 | local err 858 | local json 859 | 860 | -- get the access token from the request 861 | local access_token, err = openidc_get_bearer_access_token(opts) 862 | if access_token == nil then 863 | return nil, err 864 | end 865 | 866 | ngx.log(ngx.DEBUG, "access_token: ", access_token) 867 | 868 | json, err = openidc.jwt_verify(access_token, opts, ...) 869 | return json, err, access_token 870 | end 871 | 872 | return openidc -------------------------------------------------------------------------------- /createUiDefinition.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/0.1.2-preview/CreateUIDefinition.MultiVm.json#", 3 | "handler": "Microsoft.Compute.MultiVm", 4 | "version": "0.1.2-preview", 5 | "parameters": { 6 | "basics": [ 7 | { 8 | "name": "userName", 9 | "type": "Microsoft.Compute.UserNameTextBox", 10 | "label": "Controller Username", 11 | "defaultValue": "", 12 | "toolTip": "Username for accessing Kubernetes cluster and Kibana. Must be a valid Ubuntu username.", 13 | "constraints": { 14 | "required": true, 15 | "regex": "^(?!(?:adm|admin|audio|backup|bin|cdrom|crontab|daemon|dialout|dip|disk|fax|floppy|fuse|games|gnats|irc|kmem|landscape|libuuid|list|lp|mail|man|messagebus|mlocate|netdev|news|nobody|nogroup|operator|plugdev|proxy|root|sasl|shadow|src|ssh|sshd|staff|sudo|sync|sys|syslog|tape|tty|users|utmp|uucp|video|voice|whoopsie|www\\-data)$)\\w+$", 16 | "validationMessage": "Username must not be a reserved Ubuntu username." 17 | }, 18 | "osPlatform": "Linux", 19 | "visible": true 20 | }, 21 | { 22 | "name": "password", 23 | "type": "Microsoft.Common.PasswordBox", 24 | "label": { 25 | "password": "Controller Password", 26 | "confirmPassword": "Confirm Controller Password" 27 | }, 28 | "toolTip": "Default password for accessing Kubernetes cluster and Kibana.", 29 | "constraints": { 30 | "required": true, 31 | "regex": "^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-\\.]).{12,}$", 32 | "validationMessage": "Password must be at least 12 characters long and contain at least one lower case, upper case, digit and special character." 33 | }, 34 | "visible": true 35 | } 36 | ], 37 | "steps": [ 38 | { 39 | "name": "commonSettings", 40 | "label": "Common Settings", 41 | "subLabel": { 42 | "preValidation": "Required", 43 | "postValidation": "Done" 44 | }, 45 | "bladeTitle": "Common Settings", 46 | "elements": [ 47 | { 48 | "name": "dnsNamePrefix", 49 | "type": "Microsoft.Common.TextBox", 50 | "label": "Dns prefix", 51 | "defaultValue": "", 52 | "toolTip": "Dns prefix for Controller and Kubernetes.", 53 | "constraints": { 54 | "required": true 55 | }, 56 | "visible": true 57 | }, 58 | { 59 | "name": "registryUrl", 60 | "type": "Microsoft.Common.TextBox", 61 | "label": "Registry url", 62 | "defaultValue": "", 63 | "toolTip": "If you want to use a public registry service e.g. Docker Hub, put repository url here. Otherwise, leave it blank.", 64 | "constraints": { 65 | "required": false 66 | }, 67 | "visible": true 68 | }, 69 | { 70 | "name": "eventHubNamespace", 71 | "type": "Microsoft.Common.TextBox", 72 | "label": "Event hub namespace", 73 | "defaultValue": "undefined", 74 | "toolTip": "Target event hub namespace. If you don't want to stream in event hub events, leave it as is.", 75 | "constraints": { 76 | "required": false 77 | }, 78 | "visible": true 79 | }, 80 | { 81 | "name": "eventHubKeyName", 82 | "type": "Microsoft.Common.TextBox", 83 | "label": "Event hub key name", 84 | "defaultValue": "undefined", 85 | "toolTip": "Name of the shared access policy. If you don't want to stream in event hub events, leave it as is.", 86 | "constraints": { 87 | "required": false 88 | }, 89 | "visible": true 90 | }, 91 | { 92 | "name": "eventHubKey", 93 | "type": "Microsoft.Common.TextBox", 94 | "label": "Event hub key value", 95 | "defaultValue": "undefined", 96 | "toolTip": "Shared access key. If you don't want to stream in event hub events, leave it as is.", 97 | "constraints": { 98 | "required": false 99 | }, 100 | "visible": true 101 | }, 102 | { 103 | "name": "eventHubEntityPath", 104 | "type": "Microsoft.Common.TextBox", 105 | "label": "List of event hubs", 106 | "defaultValue": "undefined", 107 | "toolTip": "Comma seperated list of event hubs of the logstash plugin input stream. If you don't want to stream in event hub events, leave it as is.", 108 | "constraints": { 109 | "required": false 110 | }, 111 | "visible": true 112 | }, 113 | { 114 | "name": "eventHubPartitionnumber", 115 | "type": "Microsoft.Common.TextBox", 116 | "label": "Event hub partition count", 117 | "defaultValue": "4", 118 | "toolTip": "Partition count of event hubs. If you don't want to stream in event hub events, leave it as is.", 119 | "constraints": { 120 | "required": false 121 | }, 122 | "visible": true 123 | }, 124 | { 125 | "name": "eventHubThreadWaitSec", 126 | "type": "Microsoft.Common.TextBox", 127 | "label": "Thread wait interval(s)", 128 | "defaultValue": "10", 129 | "toolTip": "Logstash event hub plugin thread wait interval in seconds. If you don't want to stream in event hub events, leave it as is.", 130 | "constraints": { 131 | "required": false 132 | }, 133 | "visible": true 134 | }, 135 | { 136 | "name": "storageAccountSku", 137 | "type": "Microsoft.Common.DropDown", 138 | "label": "Data node storage account sku", 139 | "toolTip": "Storage account sku to be used as Elasticsearch data node.
For detailed information about storage account sku, refer to [Azure Storage Account Replication](https://docs.microsoft.com/en-us/azure/storage/storage-introduction#replication-for-durability-and-high-availability).", 140 | "defaultValue": "Standard locally-redundant storage(Standard_LRS)", 141 | "constraints": { 142 | "required": true, 143 | "allowedValues": [ 144 | { 145 | "label": "Standard locally-redundant storage(Standard_LRS)", 146 | "value": "Standard_LRS" 147 | }, 148 | { 149 | "label": "Premium locally-redundant storage(Premium_LRS)", 150 | "value": "Premium_LRS" 151 | } 152 | ] 153 | } 154 | }, 155 | { 156 | "name": "authenticationMode", 157 | "type": "Microsoft.Common.OptionsGroup", 158 | "label": "Authentication Mode", 159 | "defaultValue": "BasicAuth", 160 | "toolTip": "Authentication mode for accessing Kubernetes dashboard.", 161 | "constraints": { 162 | "required": true, 163 | "allowedValues": [ 164 | { 165 | "label": "Basic Authentication", 166 | "value": "BasicAuth" 167 | }, 168 | { 169 | "label": "Azure Active Directory", 170 | "value": "AzureAD" 171 | } 172 | ] 173 | }, 174 | "visible": true 175 | }, 176 | { 177 | "name": "azureAdClientId", 178 | "type": "Microsoft.Common.TextBox", 179 | "label": "Azure AD client ID", 180 | "defaultValue": "undefined", 181 | "toolTip": "Azure AD client ID(Application ID). If deploying with BasicAuth, leave it as is.", 182 | "constraints": { 183 | "required": false 184 | }, 185 | "visible": "[equals('AzureAD', steps('commonSettings').authenticationMode)]" 186 | }, 187 | { 188 | "name": "azureAdClientSecret", 189 | "type": "Microsoft.Common.TextBox", 190 | "label": "Azure AD client secret", 191 | "defaultValue": "undefined", 192 | "toolTip": "Azure AD client secret/key. If deploying with BasicAuth, leave it as is.", 193 | "constraints": { 194 | "required": false 195 | }, 196 | "visible": "[equals('AzureAD', steps('commonSettings').authenticationMode)]" 197 | }, 198 | { 199 | "name": "tenant", 200 | "type": "Microsoft.Common.TextBox", 201 | "label": "Azure AD tenant", 202 | "defaultValue": "undefined", 203 | "toolTip": "Azure AD tenant(e.g. contoso.onmicrosoft.com). If deploying with BasicAuth, leave it as is.", 204 | "constraints": { 205 | "required": false 206 | }, 207 | "visible": "[equals('AzureAD', steps('commonSettings').authenticationMode)]" 208 | } 209 | ] 210 | }, 211 | { 212 | "name": "k8sSettings", 213 | "label": "Kubernetes Cluster Settings", 214 | "subLabel": { 215 | "preValidation": "Required", 216 | "postValidation": "Done" 217 | }, 218 | "bladeTitle": "Kubernetes Cluster Settings", 219 | "elements": [ 220 | { 221 | "name": "agentCount", 222 | "type": "Microsoft.Common.DropDown", 223 | "label": "Agent Count", 224 | "toolTip": "The number of agent nodes of Kubernetes cluster.", 225 | "defaultValue": "1", 226 | "constraints": { 227 | "allowedValues": [ 228 | { 229 | "label": "1", 230 | "value": 1 231 | }, 232 | { 233 | "label": "2", 234 | "value": 2 235 | }, 236 | { 237 | "label": "3", 238 | "value": 3 239 | }, 240 | { 241 | "label": "4", 242 | "value": 4 243 | }, 244 | { 245 | "label": "5", 246 | "value": 5 247 | }, 248 | { 249 | "label": "6", 250 | "value": 6 251 | }, 252 | { 253 | "label": "7", 254 | "value": 7 255 | }, 256 | { 257 | "label": "8", 258 | "value": 8 259 | }, 260 | { 261 | "label": "9", 262 | "value": 9 263 | }, 264 | { 265 | "label": "10", 266 | "value": 10 267 | }, 268 | { 269 | "label": "11", 270 | "value": 11 271 | }, 272 | { 273 | "label": "12", 274 | "value": 12 275 | }, 276 | { 277 | "label": "13", 278 | "value": 13 279 | }, 280 | { 281 | "label": "14", 282 | "value": 14 283 | }, 284 | { 285 | "label": "15", 286 | "value": 15 287 | }, 288 | { 289 | "label": "16", 290 | "value": 16 291 | }, 292 | { 293 | "label": "17", 294 | "value": 17 295 | }, 296 | { 297 | "label": "18", 298 | "value": 18 299 | }, 300 | { 301 | "label": "19", 302 | "value": 19 303 | }, 304 | { 305 | "label": "20", 306 | "value": 20 307 | }, 308 | { 309 | "label": "21", 310 | "value": 21 311 | }, 312 | { 313 | "label": "22", 314 | "value": 22 315 | }, 316 | { 317 | "label": "23", 318 | "value": 23 319 | }, 320 | { 321 | "label": "24", 322 | "value": 24 323 | }, 324 | { 325 | "label": "25", 326 | "value": 25 327 | }, 328 | { 329 | "label": "26", 330 | "value": 26 331 | }, 332 | { 333 | "label": "27", 334 | "value": 27 335 | }, 336 | { 337 | "label": "28", 338 | "value": 28 339 | }, 340 | { 341 | "label": "29", 342 | "value": 29 343 | }, 344 | { 345 | "label": "30", 346 | "value": 30 347 | }, 348 | { 349 | "label": "31", 350 | "value": 31 351 | }, 352 | { 353 | "label": "32", 354 | "value": 32 355 | }, 356 | { 357 | "label": "33", 358 | "value": 33 359 | }, 360 | { 361 | "label": "34", 362 | "value": 34 363 | }, 364 | { 365 | "label": "35", 366 | "value": 35 367 | }, 368 | { 369 | "label": "36", 370 | "value": 36 371 | }, 372 | { 373 | "label": "37", 374 | "value": 37 375 | }, 376 | { 377 | "label": "38", 378 | "value": 38 379 | }, 380 | { 381 | "label": "39", 382 | "value": 39 383 | }, 384 | { 385 | "label": "40", 386 | "value": 40 387 | }, 388 | { 389 | "label": "41", 390 | "value": 41 391 | }, 392 | { 393 | "label": "42", 394 | "value": 42 395 | }, 396 | { 397 | "label": "43", 398 | "value": 43 399 | }, 400 | { 401 | "label": "44", 402 | "value": 44 403 | }, 404 | { 405 | "label": "45", 406 | "value": 45 407 | }, 408 | { 409 | "label": "46", 410 | "value": 46 411 | }, 412 | { 413 | "label": "47", 414 | "value": 47 415 | }, 416 | { 417 | "label": "48", 418 | "value": 48 419 | }, 420 | { 421 | "label": "49", 422 | "value": 49 423 | }, 424 | { 425 | "label": "50", 426 | "value": 50 427 | }, 428 | { 429 | "label": "51", 430 | "value": 51 431 | }, 432 | { 433 | "label": "52", 434 | "value": 52 435 | }, 436 | { 437 | "label": "53", 438 | "value": 53 439 | }, 440 | { 441 | "label": "54", 442 | "value": 54 443 | }, 444 | { 445 | "label": "55", 446 | "value": 55 447 | }, 448 | { 449 | "label": "56", 450 | "value": 56 451 | }, 452 | { 453 | "label": "57", 454 | "value": 57 455 | }, 456 | { 457 | "label": "58", 458 | "value": 58 459 | }, 460 | { 461 | "label": "59", 462 | "value": 59 463 | }, 464 | { 465 | "label": "60", 466 | "value": 60 467 | }, 468 | { 469 | "label": "61", 470 | "value": 61 471 | }, 472 | { 473 | "label": "62", 474 | "value": 62 475 | }, 476 | { 477 | "label": "63", 478 | "value": 63 479 | }, 480 | { 481 | "label": "64", 482 | "value": 64 483 | }, 484 | { 485 | "label": "65", 486 | "value": 65 487 | }, 488 | { 489 | "label": "66", 490 | "value": 66 491 | }, 492 | { 493 | "label": "67", 494 | "value": 67 495 | }, 496 | { 497 | "label": "68", 498 | "value": 68 499 | }, 500 | { 501 | "label": "69", 502 | "value": 69 503 | }, 504 | { 505 | "label": "70", 506 | "value": 70 507 | }, 508 | { 509 | "label": "71", 510 | "value": 71 511 | }, 512 | { 513 | "label": "72", 514 | "value": 72 515 | }, 516 | { 517 | "label": "73", 518 | "value": 73 519 | }, 520 | { 521 | "label": "74", 522 | "value": 74 523 | }, 524 | { 525 | "label": "75", 526 | "value": 75 527 | }, 528 | { 529 | "label": "76", 530 | "value": 76 531 | }, 532 | { 533 | "label": "77", 534 | "value": 77 535 | }, 536 | { 537 | "label": "78", 538 | "value": 78 539 | }, 540 | { 541 | "label": "79", 542 | "value": 79 543 | }, 544 | { 545 | "label": "80", 546 | "value": 80 547 | }, 548 | { 549 | "label": "81", 550 | "value": 81 551 | }, 552 | { 553 | "label": "82", 554 | "value": 82 555 | }, 556 | { 557 | "label": "83", 558 | "value": 83 559 | }, 560 | { 561 | "label": "84", 562 | "value": 84 563 | }, 564 | { 565 | "label": "85", 566 | "value": 85 567 | }, 568 | { 569 | "label": "86", 570 | "value": 86 571 | }, 572 | { 573 | "label": "87", 574 | "value": 87 575 | }, 576 | { 577 | "label": "88", 578 | "value": 88 579 | }, 580 | { 581 | "label": "89", 582 | "value": 89 583 | }, 584 | { 585 | "label": "90", 586 | "value": 90 587 | }, 588 | { 589 | "label": "91", 590 | "value": 91 591 | }, 592 | { 593 | "label": "92", 594 | "value": 92 595 | }, 596 | { 597 | "label": "93", 598 | "value": 93 599 | }, 600 | { 601 | "label": "94", 602 | "value": 94 603 | }, 604 | { 605 | "label": "95", 606 | "value": 95 607 | }, 608 | { 609 | "label": "96", 610 | "value": 96 611 | }, 612 | { 613 | "label": "97", 614 | "value": 97 615 | }, 616 | { 617 | "label": "98", 618 | "value": 98 619 | }, 620 | { 621 | "label": "99", 622 | "value": 99 623 | }, 624 | { 625 | "label": "100", 626 | "value": 100 627 | } 628 | ] 629 | }, 630 | "visible": true 631 | }, 632 | { 633 | "name": "agentVMSize", 634 | "type": "Microsoft.Compute.SizeSelector", 635 | "label": "Agent Node Size", 636 | "toolTip": "VM size of agent node.", 637 | "recommendedSizes": [ 638 | "Standard_DS2_v2", 639 | "Standard_DS3_v2" 640 | ], 641 | "constraints": { 642 | "allowedSizes": [ 643 | "Standard_A0", 644 | "Standard_A1", 645 | "Standard_A2", 646 | "Standard_A3", 647 | "Standard_A4", 648 | "Standard_A1_v2", 649 | "Standard_A2_v2", 650 | "Standard_A4_v2", 651 | "Standard_A8_v2", 652 | "Standard_A2m_v2", 653 | "Standard_A4m_v2", 654 | "Standard_A8m_v2", 655 | "Standard_D2", 656 | "Standard_D3", 657 | "Standard_D4", 658 | "Standard_D11", 659 | "Standard_D12", 660 | "Standard_D13", 661 | "Standard_D14", 662 | "Standard_D2_v2", 663 | "Standard_D3_v2", 664 | "Standard_D4_v2", 665 | "Standard_D5_v2", 666 | "Standard_D11_v2", 667 | "Standard_D12_v2", 668 | "Standard_D13_v2", 669 | "Standard_D14_v2", 670 | "Standard_DS2", 671 | "Standard_DS3", 672 | "Standard_DS4", 673 | "Standard_DS11", 674 | "Standard_DS12", 675 | "Standard_DS13", 676 | "Standard_DS14", 677 | "Standard_DS2_v2", 678 | "Standard_DS3_v2", 679 | "Standard_DS4_v2", 680 | "Standard_DS5_v2", 681 | "Standard_DS11_v2", 682 | "Standard_DS12_v2", 683 | "Standard_DS13_v2", 684 | "Standard_DS14_v2", 685 | "Standard_G1", 686 | "Standard_G2", 687 | "Standard_G3", 688 | "Standard_G4", 689 | "Standard_G5", 690 | "Standard_GS1", 691 | "Standard_GS2", 692 | "Standard_GS3", 693 | "Standard_GS4", 694 | "Standard_GS5" 695 | ] 696 | }, 697 | "osPlatform": "Linux", 698 | "count": "[steps('k8sSettings').agentCount]", 699 | "visible": true 700 | }, 701 | { 702 | "name": "masterCount", 703 | "type": "Microsoft.Common.DropDown", 704 | "label": "Master Count", 705 | "toolTip": "The number of master nodes of Kubernetes cluster. This value can only be 1, 3 or 5.", 706 | "defaultValue": "1", 707 | "constraints": { 708 | "allowedValues": [ 709 | { 710 | "label": "1", 711 | "value": 1 712 | }, 713 | { 714 | "label": "3", 715 | "value": 3 716 | }, 717 | { 718 | "label": "5", 719 | "value": 5 720 | } 721 | ] 722 | }, 723 | "visible": true 724 | } 725 | ] 726 | }, 727 | { 728 | "name": "securitySettings", 729 | "label": "Security Settings", 730 | "subLabel": { 731 | "preValidation": "Required", 732 | "postValidation": "Done" 733 | }, 734 | "bladeTitle": "Security Settings", 735 | "elements": [ 736 | { 737 | "name": "sshRSAPublicKey", 738 | "osPlatform": "Linux", 739 | "label": { 740 | "authenticationType": "", 741 | "password": "", 742 | "confirmPassword": "", 743 | "sshPublicKey": "SSH public key" 744 | }, 745 | "toolTip": { 746 | "sshPublicKey": "SSH public key for Controller communicating with Kubernetes cluster.
[Generating SSH key pair online](https://microsoft.github.io/elk-acs-kubernetes/)." 747 | }, 748 | "type": "Microsoft.Compute.CredentialsCombo", 749 | "constraints": { 750 | "required": true 751 | }, 752 | "options": { 753 | "hidePassword": true 754 | }, 755 | "visible": true 756 | }, 757 | { 758 | "name": "privateKey", 759 | "type": "Microsoft.Common.TextBox", 760 | "label": "Base64 encoded SSH private key", 761 | "toolTip": "SSH private key for Controller communicating with Kubernetes cluster. The key must be base64 encoded.
[Generating SSH key pair online](https://microsoft.github.io/elk-acs-kubernetes/).", 762 | "constraints": { 763 | "required": true 764 | }, 765 | "visible": true 766 | }, 767 | { 768 | "name": "servicePrincipalClientId", 769 | "type": "Microsoft.Common.TextBox", 770 | "label": "Service principal client ID", 771 | "toolTip": "The Client ID of service principal object for accessing Azure resources.
[Creating a Service Principal](https://go.microsoft.com/fwlink/?linkid=834427).", 772 | "constraints": { 773 | "required": true 774 | }, 775 | "visible": true 776 | }, 777 | { 778 | "name": "servicePrincipalClientSecret", 779 | "type": "Microsoft.Common.TextBox", 780 | "label": "Service principal client secret", 781 | "toolTip": "The Client Secret of the service principal object for accessing Azure resources.
[Creating a Service Principal](https://go.microsoft.com/fwlink/?linkid=834427).", 782 | "constraints": { 783 | "required": true 784 | }, 785 | "visible": true 786 | } 787 | ] 788 | } 789 | ], 790 | "outputs": { 791 | "dnsNamePrefix": "[steps('commonSettings').dnsNamePrefix]", 792 | "registryUrl": "[steps('commonSettings').registryUrl]", 793 | "eventHubNamespace": "[steps('commonSettings').eventHubNamespace]", 794 | "eventHubKeyName": "[steps('commonSettings').eventHubKeyName]", 795 | "eventHubKey": "[steps('commonSettings').eventHubKey]", 796 | "eventHubEntityPath": "[steps('commonSettings').eventHubEntityPath]", 797 | "eventHubPartitionnumber": "[steps('commonSettings').eventHubPartitionnumber]", 798 | "eventHubThreadWaitSec": "[steps('commonSettings').eventHubThreadWaitSec]", 799 | "storageAccountSku": "[steps('commonSettings').storageAccountSku]", 800 | "authenticationMode": "[steps('commonSettings').authenticationMode]", 801 | "azureAdClientId": "[steps('commonSettings').azureAdClientId]", 802 | "azureAdClientSecret": "[steps('commonSettings').azureAdClientSecret]", 803 | "tenant": "[steps('commonSettings').tenant]", 804 | "agentCount": "[steps('k8sSettings').agentCount]", 805 | "agentVMSize": "[steps('k8sSettings').agentVMSize]", 806 | "linuxAdminUsername": "[basics('userName')]", 807 | "masterCount": "[steps('k8sSettings').masterCount]", 808 | "sshRSAPublicKey": "[steps('securitySettings').sshRSAPublicKey.sshPublicKey]", 809 | "privateKey": "[steps('securitySettings').privateKey]", 810 | "servicePrincipalClientId": "[steps('securitySettings').servicePrincipalClientId]", 811 | "servicePrincipalClientSecret": "[steps('securitySettings').servicePrincipalClientSecret]", 812 | "adminPassword": "[basics('password')]", 813 | "location": "[location()]" 814 | } 815 | } 816 | } 817 | -------------------------------------------------------------------------------- /docker/elasticsearch/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM java:8u111-jre 2 | 3 | # Download & Configure elasticsearch 4 | EXPOSE 9200 9300 5 | 6 | ENV VERSION 5.4.0 7 | ENV PLATFORM linux-x86_64 8 | ENV DOWNLOAD_URL "https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${VERSION}.tar.gz" 9 | 10 | RUN cd /tmp \ 11 | && apt update && apt install -y sudo uuid-runtime \ 12 | && echo "Install Elasticsearch..." \ 13 | && wget -O elasticsearch.tar.gz "$DOWNLOAD_URL" \ 14 | && tar -xf elasticsearch.tar.gz \ 15 | && mv elasticsearch-$VERSION /elasticsearch 16 | 17 | WORKDIR /elasticsearch 18 | 19 | COPY config /elasticsearch/config 20 | 21 | RUN adduser --disabled-password --no-create-home --gecos "" --shell /sbin/nologin elasticsearch \ 22 | && chown -R elasticsearch:elasticsearch /elasticsearch 23 | 24 | ENV CLUSTER_NAME elasticsearch 25 | ENV NODE_MASTER true 26 | ENV NODE_DATA true 27 | ENV NODE_INGEST true 28 | ENV NETWORK_HOST 0.0.0.0 29 | ENV DISCOVERY_SERVICE localhost 30 | ENV NUMBER_OF_MASTERS 1 31 | 32 | COPY run.sh / 33 | RUN chmod +x /run.sh 34 | CMD ["/run.sh"] 35 | -------------------------------------------------------------------------------- /docker/elasticsearch/config/elasticsearch.yml: -------------------------------------------------------------------------------- 1 | cluster: 2 | name: ${CLUSTER_NAME} 3 | 4 | node: 5 | master: ${NODE_MASTER} 6 | name: ${NODE_NAME} 7 | data: ${NODE_DATA} 8 | ingest: ${NODE_INGEST} 9 | 10 | network.host: ${NETWORK_HOST} 11 | 12 | indices.breaker.fielddata.limit: 85% 13 | indices.fielddata.cache.size: 75% 14 | 15 | path: 16 | data: /data/data 17 | logs: /data/log 18 | 19 | http: 20 | enabled: true 21 | compression: true 22 | 23 | discovery: 24 | zen: 25 | ping.unicast.hosts: ${DISCOVERY_SERVICE} 26 | minimum_master_nodes: ${NUMBER_OF_MASTERS} -------------------------------------------------------------------------------- /docker/elasticsearch/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -z "${NODE_NAME}" ]; then 4 | NODE_NAME=$(uuidgen) 5 | fi 6 | export NODE_NAME=${NODE_NAME} 7 | 8 | mkdir -p /data 9 | chown -R elasticsearch:elasticsearch /data 10 | 11 | sudo -E -u elasticsearch /elasticsearch/bin/elasticsearch 12 | -------------------------------------------------------------------------------- /docker/filebeat/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu 2 | 3 | ENV FILEBEAT_VERSION 5.4.1 4 | 5 | RUN apt-get update && \ 6 | apt-get -y install wget && \ 7 | wget https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-${FILEBEAT_VERSION}-linux-x86_64.tar.gz && \ 8 | echo "$(wget -qO - https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-${FILEBEAT_VERSION}-linux-x86_64.tar.gz.sha1) filebeat-${FILEBEAT_VERSION}-linux-x86_64.tar.gz" | sha1sum -c - && \ 9 | tar xzf filebeat-${FILEBEAT_VERSION}-linux-x86_64.tar.gz && \ 10 | mv filebeat-${FILEBEAT_VERSION}-linux-x86_64/filebeat /usr/local/bin && \ 11 | rm -rf /filebeat* && \ 12 | apt-get -y remove wget && \ 13 | apt-get -y autoremove 14 | 15 | RUN apt-get -y install curl 16 | 17 | COPY filebeat.yml /etc/filebeat/ 18 | 19 | CMD ["/usr/local/bin/filebeat", "-e", "-c", "/etc/filebeat/filebeat.yml"] -------------------------------------------------------------------------------- /docker/filebeat/filebeat.yml: -------------------------------------------------------------------------------- 1 | filebeat.registry_file: /var/log/containers/filebeat_registry 2 | 3 | filebeat.prospectors: 4 | - 5 | paths: 6 | - "/var/log/*.log" 7 | - "/var/log/containers/*.log" 8 | - "/var/log/pods/*.log" 9 | - "/var/lib/docker/containers/*.log" 10 | fields: 11 | host: ${FILEBEAT_HOST:${HOSTNAME}} 12 | fields_under_root: true 13 | 14 | output.logstash: 15 | hosts: ["logstash:5043"] 16 | timeout: 15 17 | # Available log levels are: critical, error, warning, info, debug 18 | logging.level: info -------------------------------------------------------------------------------- /docker/kibana/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM java:8u111-jre 2 | 3 | # Download & Configure kibana 4 | EXPOSE 80 5 | 6 | ENV KIBANA_VERSION 5.4.0 7 | ENV PLATFORM linux-x86_64 8 | ENV DOWNLOAD_URL "https://artifacts.elastic.co/downloads/kibana/kibana-${KIBANA_VERSION}-${PLATFORM}.tar.gz" 9 | 10 | RUN cd /tmp \ 11 | && echo "Install Kibana..." \ 12 | && wget -O kibana.tar.gz "$DOWNLOAD_URL" \ 13 | && tar -xf kibana.tar.gz \ 14 | && mv kibana-$KIBANA_VERSION-$PLATFORM /kibana 15 | 16 | RUN apt-get update && apt-get install -y nginx apache2-utils 17 | 18 | COPY nginx-site.conf /etc/nginx/sites-available/default 19 | 20 | ENV SERVER_PORT 5601 21 | ENV SERVER_HOST "localhost" 22 | ENV ELASTICSEARCH_URL "http://elasticsearch:9200" 23 | 24 | COPY run.sh /run.sh 25 | RUN chmod +x /run.sh 26 | 27 | CMD ["/run.sh"] -------------------------------------------------------------------------------- /docker/kibana/nginx-site.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | listen [::]:80 default_server; 4 | 5 | server_name _; 6 | 7 | location / { 8 | proxy_pass http://localhost:5601; 9 | auth_basic "Restrict Access"; 10 | auth_basic_user_file /etc/nginx/.htpasswd; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /docker/kibana/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ${PASSWORD:=azureuser} 4 | ${USERNAME:=azureuser} 5 | ${SERVER_HOST:=0.0.0.0} 6 | ${SERVER_PORT:=5601} 7 | ${ELASTICSEARCH_URL:=http://elasticsearch:9200} 8 | 9 | echo ${PASSWORD} | htpasswd -c -i /etc/nginx/.htpasswd ${USERNAME} 10 | 11 | service nginx start 12 | service nginx reload 13 | /kibana/bin/kibana --server.host=${SERVER_HOST} --server.port=${SERVER_PORT} --elasticsearch.url=${ELASTICSEARCH_URL} -------------------------------------------------------------------------------- /docker/logstash/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | # Download & Configure logstash 4 | # beats input on 5043 5 | EXPOSE 5043 6 | 7 | # Install Utilities 8 | RUN echo "Installing utilities." 9 | RUN apt-get update \ 10 | && apt-get -y --force-yes install software-properties-common python-software-properties debconf-utils wget 11 | 12 | # Install Java 13 | RUN echo "Installing Java." 14 | RUN add-apt-repository -y ppa:openjdk-r/ppa \ 15 | && apt-get update \ 16 | && apt-get install -y openjdk-8-jre 17 | 18 | ENV VERSION 5.4.0 19 | ENV PLATFORM linux-x86_64 20 | ENV DOWNLOAD_URL "https://artifacts.elastic.co/downloads/logstash/logstash-${VERSION}.tar.gz" 21 | 22 | RUN cd /tmp \ 23 | && echo "Install Logstash..." \ 24 | && wget -O logstash.tar.gz "$DOWNLOAD_URL" \ 25 | && tar -xf logstash.tar.gz \ 26 | && mv logstash-$VERSION /logstash 27 | 28 | RUN echo "Installing Azure WAD Event Hub Plugin..." \ 29 | && /logstash/bin/logstash-plugin install logstash-input-azurewadeventhub 30 | 31 | COPY run.sh /run.sh 32 | RUN chmod +x /run.sh 33 | 34 | ARG DIAG_EVT_HUB_NS=undefined 35 | ARG DIAG_EVT_HUB_KEY_NAME=undefined 36 | ARG DIAG_EVT_HUB_ACC_KEY=undefined 37 | ARG DIAG_EVT_HUB_ENT_PATH=undefined 38 | ARG DIAG_EVT_HUB_PART=4 39 | ARG DIAG_EVT_HUB_THR_WAIT=1 40 | 41 | ENV EVT_HUB_NS ${DIAG_EVT_HUB_NS} 42 | ENV EVT_HUB_KEY_NAME ${DIAG_EVT_HUB_KEY_NAME} 43 | ENV EVT_HUB_ACC_KEY ${DIAG_EVT_HUB_ACC_KEY} 44 | ENV EVT_HUB_ENT_PATH ${DIAG_EVT_HUB_ENT_PATH} 45 | ENV EVT_HUB_PART ${DIAG_EVT_HUB_PART} 46 | ENV EVT_HUB_THR_WAIT ${DIAG_EVT_HUB_THR_WAIT} 47 | 48 | ENV ELASTICSEARCH_URL "http://elasticsearch:9200" 49 | 50 | CMD ["/run.sh"] 51 | -------------------------------------------------------------------------------- /docker/logstash/pipeline/logstash.conf: -------------------------------------------------------------------------------- 1 | input { 2 | beats { 3 | host => "0.0.0.0" 4 | port => 5043 5 | } 6 | } 7 | 8 | output { 9 | elasticsearch { 10 | hosts => [ "${ELASTICSEARCH_URL}" ] 11 | } 12 | } -------------------------------------------------------------------------------- /docker/logstash/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | log() 3 | { 4 | echo "$1" 5 | } 6 | 7 | # The following environment variables have been set 8 | # EVT_HUB_NS ${DIAG_EVT_HUB_NS} 9 | # EVT_HUB_KEY_NAME ${DIAG_EVT_HUB_KEY_NAME} 10 | # EVT_HUB_ACC_KEY ${DIAG_EVT_HUB_ACC_KEY} 11 | # EVT_HUB_ENT_PATH ${DIAG_EVT_HUB_ENT_PATH} 12 | # EVT_HUB_PART ${DIAG_EVT_HUB_PART} 13 | # EVT_HUB_THR_WAIT ${DIAG_EVT_HUB_THR_WAIT} 14 | # ELASTICSEARCH_URL "http://elasticsearch:9200" 15 | 16 | # No EH provided 17 | echo "input {" > /logstash/config/logstash.conf 18 | echo " beats { host => \"0.0.0.0\" port => 5043 tags => ['beats']}" >> /logstash/config/logstash.conf 19 | if [ ! -z '$EVT_HUB_ACC_KEY' -a '$EVT_HUB_ACC_KEY' != 'undefined' ] && [ ! -z '$EVT_HUB_KEY_NAME' -a '$EVT_HUB_KEY_NAME' != 'undefined' ] && [ ! -z '$EVT_HUB_ENT_PATH' -a '$EVT_HUB_ENT_PATH' != 'undefined' ] && [ ! -z '$EVT_HUB_NS' -a '$EVT_HUB_NS' != 'undefined' ] && [ ! -z '$EVT_HUB_PART' -a '$EVT_HUB_PART' != 'undefined' ] && [ ! -z '$EVT_HUB_THR_WAIT' -a '$EVT_HUB_THR_WAIT' != 'undefined' ]; then 20 | for eventHub in $(echo $EVT_HUB_ENT_PATH | sed "s/,/ /g") 21 | do 22 | echo " azurewadeventhub {key => '$EVT_HUB_ACC_KEY' username => '$EVT_HUB_KEY_NAME' eventhub => '$eventHub' namespace => '$EVT_HUB_NS' partitions => $EVT_HUB_PART thread_wait_sec => $EVT_HUB_THR_WAIT tags => ['wad']}" >> /logstash/config/logstash.conf 23 | done 24 | fi 25 | echo "}" >> /logstash/config/logstash.conf 26 | echo "output {" >> /logstash/config/logstash.conf 27 | echo " if [tags][0] == 'beats' {" >> /logstash/config/logstash.conf 28 | echo " elasticsearch {hosts => ['$ELASTICSEARCH_URL']}" >> /logstash/config/logstash.conf 29 | echo " } else if [tags][0] == 'wad' {" >> /logstash/config/logstash.conf 30 | echo " elasticsearch {hosts => ['$ELASTICSEARCH_URL'] index => 'wad'}" >> /logstash/config/logstash.conf 31 | echo " } else {" >> /logstash/config/logstash.conf 32 | echo " file {" >> /logstash/config/logstash.conf 33 | echo " path => '/var/log/logstash/other.log'" >> /logstash/config/logstash.conf 34 | echo " }" >> /logstash/config/logstash.conf 35 | echo " }" >> /logstash/config/logstash.conf 36 | echo "}" >> /logstash/config/logstash.conf 37 | 38 | log "Output logstash.conf" 39 | cat /logstash/config/logstash.conf 40 | 41 | # Configure Start 42 | log "Configure start up service" 43 | /logstash/bin/logstash -r -f /logstash/config/logstash.conf -------------------------------------------------------------------------------- /docker/push-images.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | echo $@ 6 | 7 | while getopts ':r:u:p:a:b:c:d:e:f:' arg 8 | do 9 | case ${arg} in 10 | r) registryUrl=${OPTARG};; 11 | u) registryUsername=${OPTARG};; 12 | p) registryPassword=${OPTARG};; 13 | a) diagEvtHubNs=${OPTARG};; 14 | b) diagEvtHubNa=${OPTARG};; 15 | c) diagEvtHubKey=${OPTARG};; 16 | d) diagEvtHubEntPa=${OPTARG};; 17 | e) diagEvtHubPartNum=${OPTARG};; 18 | f) diagEvtHubThreadWait=${OPTARG};; 19 | esac 20 | done 21 | 22 | docker login --username ${registryUsername} --password ${registryPassword} ${registryUrl} 23 | 24 | docker build -t ${registryUrl}/elasticsearch ./elasticsearch 25 | docker push ${registryUrl}/elasticsearch 26 | docker build -t ${registryUrl}/kibana ./kibana 27 | docker push ${registryUrl}/kibana 28 | docker build -t ${registryUrl}/logstash ./logstash --build-arg DIAG_EVT_HUB_NS=${diagEvtHubNs} \ 29 | --build-arg DIAG_EVT_HUB_KEY_NAME=${diagEvtHubNa} \ 30 | --build-arg DIAG_EVT_HUB_ACC_KEY=${diagEvtHubKey} \ 31 | --build-arg DIAG_EVT_HUB_ENT_PATH=${diagEvtHubEntPa} \ 32 | --build-arg DIAG_EVT_HUB_PART=${diagEvtHubPartNum} \ 33 | --build-arg DIAG_EVT_HUB_THR_WAIT=${diagEvtHubThreadWait} 34 | docker push ${registryUrl}/logstash 35 | docker build -t ${registryUrl}/filebeat:1.0.0 ./filebeat 36 | docker push ${registryUrl}/filebeat 37 | -------------------------------------------------------------------------------- /helm-charts/config.yaml: -------------------------------------------------------------------------------- 1 | common: 2 | namespace: ${NAMESPACE} 3 | secretName: azure-registry 4 | registry: ${REGISTRY_URL} 5 | 6 | elasticsearch: 7 | image: 8 | tag: ${TAG} 9 | client: 10 | replicas: 2 11 | master: 12 | replicas: 3 13 | data: 14 | replicas: 2 15 | storageAccount: ${STORAGE_ACCOUNT} 16 | location: ${STORAGE_LOCATION} 17 | storageSku: ${STORAGE_SKU} 18 | 19 | kibana: 20 | image: 21 | tag: ${TAG} 22 | replicaCount: 1 23 | env: 24 | USERNAME: ${USERNAME} 25 | PASSWORD: '${PASSWORD}' 26 | 27 | logstash: 28 | image: 29 | tag: ${TAG} 30 | replicaCount: 1 31 | 32 | filebeat: 33 | image: 34 | tag: 1.0.0 35 | 36 | -------------------------------------------------------------------------------- /helm-charts/es/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: helm chart for elasticSearch 3 | name: elasticsearch 4 | version: 0.0.1 5 | maintainers: 6 | - name: Yawei Wang 7 | email: yaweiw@microsoft.com 8 | engine: gotpl 9 | -------------------------------------------------------------------------------- /helm-charts/es/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 53 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 53 chars (63 - len("-discovery")) because some Kubernetes name fields are limited to 63 (by the DNS naming spec). 12 | */}} 13 | {{- define "fullname" -}} 14 | {{- default .Chart.Name .Values.nameOverride | trunc 53 | trimSuffix "-" -}} 15 | {{- end -}} 16 | -------------------------------------------------------------------------------- /helm-charts/es/templates/az-storageclass.yaml: -------------------------------------------------------------------------------- 1 | # StorageClass 2 | kind: StorageClass 3 | apiVersion: storage.k8s.io/v1beta1 4 | metadata: 5 | name: data 6 | namespace: {{ .Values.common.namespace }} 7 | provisioner: kubernetes.io/azure-disk 8 | parameters: 9 | skuName: {{ .Values.elasticsearch.data.storageSku }} 10 | location: {{ .Values.elasticsearch.data.location }} 11 | storageAccount: {{ .Values.elasticsearch.data.storageAccount }} 12 | -------------------------------------------------------------------------------- /helm-charts/es/templates/es-client-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | name: {{template "fullname" .}}-client 5 | namespace: {{ .Values.common.namespace }} 6 | labels: 7 | component: {{template "fullname" .}} 8 | role: client 9 | spec: 10 | replicas: {{ .Values.elasticsearch.client.replicas }} 11 | template: 12 | metadata: 13 | labels: 14 | component: {{template "fullname" .}} 15 | role: client 16 | annotations: 17 | # Elasticsearch uses a hybrid mmapfs / niofs directory by default to 18 | # store its indices. The default operating system limits on mmap counts 19 | # is likely to be too low, which may result in out of memory exceptions, 20 | # so we use vm.max_map_count=262144 to increase that value. 21 | pod.beta.kubernetes.io/init-containers: '[ 22 | { 23 | "name": "sysctl", 24 | "image": "busybox", 25 | "imagePullPolicy": "IfNotPresent", 26 | "command": ["sysctl", "-w", "vm.max_map_count=262144"], 27 | "securityContext": { 28 | "privileged": true 29 | } 30 | } 31 | ]' 32 | spec: 33 | containers: 34 | - name: es-client 35 | securityContext: 36 | privileged: false 37 | capabilities: 38 | add: 39 | - IPC_LOCK 40 | - SYS_RESOURCE 41 | image: "{{ .Values.common.registry }}/elasticsearch:{{ .Values.elasticsearch.image.tag }}" 42 | imagePullPolicy: {{ .Values.elasticsearch.image.pullPolicy }} 43 | resources: 44 | limits: 45 | memory: "1024Mi" 46 | requests: 47 | memory: "256Mi" 48 | env: 49 | - name: NAMESPACE 50 | valueFrom: 51 | fieldRef: 52 | fieldPath: metadata.namespace 53 | - name: NODE_NAME 54 | valueFrom: 55 | fieldRef: 56 | fieldPath: metadata.name 57 | - name: DISCOVERY_SERVICE 58 | value: {{template "fullname" .}}-discovery 59 | {{- range $key, $value := .Values.common.env }} 60 | - name: {{ $key | upper | replace "-" "_" }} 61 | value: {{ $value | quote }} 62 | {{- end }} 63 | {{- range $key, $value := .Values.elasticsearch.client.env }} 64 | - name: {{ $key | upper | replace "-" "_" }} 65 | value: {{ $value | quote }} 66 | {{- end }} 67 | ports: 68 | - containerPort: 9200 69 | name: http 70 | protocol: TCP 71 | - containerPort: 9300 72 | name: transport 73 | protocol: TCP 74 | volumeMounts: 75 | - name: storage 76 | mountPath: /data 77 | imagePullSecrets: 78 | - name: {{ .Values.common.secretName }} 79 | volumes: 80 | - emptyDir: 81 | medium: "" 82 | name: "storage" 83 | -------------------------------------------------------------------------------- /helm-charts/es/templates/es-data-service.yaml: -------------------------------------------------------------------------------- 1 | # Headless Service 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{template "fullname" .}}-data 6 | namespace: {{ .Values.common.namespace }} 7 | labels: 8 | component: {{template "fullname" .}} 9 | role: data 10 | spec: 11 | ports: 12 | - port: 9300 13 | name: transport 14 | clusterIP: None 15 | selector: 16 | component: {{template "fullname" .}} 17 | role: data -------------------------------------------------------------------------------- /helm-charts/es/templates/es-data-statefulset.yaml: -------------------------------------------------------------------------------- 1 | # StatefulSet 2 | apiVersion: apps/v1beta1 3 | kind: StatefulSet 4 | metadata: 5 | name: {{template "fullname" .}}-data 6 | namespace: {{ .Values.common.namespace }} 7 | labels: 8 | component: {{template "fullname" .}} 9 | role: data 10 | spec: 11 | serviceName: {{template "fullname" .}}-data 12 | replicas: {{.Values.elasticsearch.data.replicas}} 13 | template: 14 | metadata: 15 | labels: 16 | component: {{template "fullname" .}} 17 | role: data 18 | annotations: 19 | pod.beta.kubernetes.io/init-containers: '[ 20 | { 21 | "name": "sysctl", 22 | "image": "busybox", 23 | "imagePullPolicy": "IfNotPresent", 24 | "command": ["sysctl", "-w", "vm.max_map_count=262144"], 25 | "securityContext": { 26 | "privileged": true 27 | } 28 | } 29 | ]' 30 | spec: 31 | containers: 32 | - name: {{template "fullname" .}}-data 33 | securityContext: 34 | privileged: true 35 | capabilities: 36 | add: 37 | - IPC_LOCK 38 | image: "{{ .Values.common.registry }}/elasticsearch:{{ .Values.elasticsearch.image.tag }}" 39 | imagePullPolicy: {{ .Values.elasticsearch.image.pullPolicy }} 40 | env: 41 | - name: NAMESPACE 42 | valueFrom: 43 | fieldRef: 44 | fieldPath: metadata.namespace 45 | - name: NODE_NAME 46 | valueFrom: 47 | fieldRef: 48 | fieldPath: metadata.name 49 | - name: DISCOVERY_SERVICE 50 | value: {{template "fullname" .}}-discovery 51 | {{- range $key, $value := .Values.common.env }} 52 | - name: {{ $key | upper | replace "-" "_" }} 53 | value: {{ $value | quote }} 54 | {{- end }} 55 | {{- range $key, $value := .Values.elasticsearch.data.env }} 56 | - name: {{ $key | upper | replace "-" "_" }} 57 | value: {{ $value | quote }} 58 | {{- end }} 59 | ports: 60 | - containerPort: 9300 61 | name: transport 62 | protocol: TCP 63 | volumeMounts: 64 | - name: es-persistent-storage 65 | mountPath: /data 66 | imagePullSecrets: 67 | - name: {{ .Values.common.secretName }} 68 | volumeClaimTemplates: 69 | - metadata: 70 | name: es-persistent-storage 71 | annotations: 72 | volume.beta.kubernetes.io/storage-class: data 73 | spec: 74 | accessModes: [ "ReadWriteOnce" ] 75 | resources: 76 | requests: 77 | storage: 50Gi 78 | -------------------------------------------------------------------------------- /helm-charts/es/templates/es-discovery-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{template "fullname" .}}-discovery 5 | namespace: {{ .Values.common.namespace }} 6 | labels: 7 | component: {{template "fullname" .}} 8 | role: master 9 | spec: 10 | selector: 11 | component: {{template "fullname" .}} 12 | role: master 13 | ports: 14 | - name: transport 15 | port: 9300 16 | protocol: TCP 17 | -------------------------------------------------------------------------------- /helm-charts/es/templates/es-master-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | name: {{template "fullname" .}}-master 5 | namespace: {{ .Values.common.namespace }} 6 | labels: 7 | component: {{template "fullname" .}} 8 | role: master 9 | spec: 10 | replicas: {{ .Values.elasticsearch.master.replicas }} 11 | template: 12 | metadata: 13 | labels: 14 | component: {{template "fullname" .}} 15 | role: master 16 | annotations: 17 | # Elasticsearch uses a hybrid mmapfs / niofs directory by default to 18 | # store its indices. The default operating system limits on mmap counts 19 | # is likely to be too low, which may result in out of memory exceptions, 20 | # so we use vm.max_map_count=262144 to increase that value. 21 | pod.beta.kubernetes.io/init-containers: '[ 22 | { 23 | "name": "sysctl", 24 | "image": "busybox", 25 | "imagePullPolicy": "IfNotPresent", 26 | "command": ["sysctl", "-w", "vm.max_map_count=262144"], 27 | "securityContext": { 28 | "privileged": true 29 | } 30 | } 31 | ]' 32 | spec: 33 | containers: 34 | - name: es-master 35 | securityContext: 36 | privileged: false 37 | capabilities: 38 | add: 39 | - IPC_LOCK 40 | - SYS_RESOURCE 41 | image: "{{ .Values.common.registry }}/elasticsearch:{{ .Values.elasticsearch.image.tag }}" 42 | imagePullPolicy: {{ .Values.elasticsearch.image.pullPolicy }} 43 | resources: 44 | limits: 45 | memory: "2048Mi" 46 | requests: 47 | memory: "512Mi" 48 | env: 49 | - name: NAMESPACE 50 | valueFrom: 51 | fieldRef: 52 | fieldPath: metadata.namespace 53 | - name: NODE_NAME 54 | valueFrom: 55 | fieldRef: 56 | fieldPath: metadata.name 57 | - name: DISCOVERY_SERVICE 58 | value: {{template "fullname" .}}-discovery 59 | {{- range $key, $value := .Values.common.env }} 60 | - name: {{ $key | upper | replace "-" "_" }} 61 | value: {{ $value | quote }} 62 | {{- end }} 63 | {{- range $key, $value := .Values.elasticsearch.master.env }} 64 | - name: {{ $key | upper | replace "-" "_" }} 65 | value: {{ $value | quote }} 66 | {{- end }} 67 | ports: 68 | - containerPort: 9300 69 | name: transport 70 | protocol: TCP 71 | volumeMounts: 72 | - name: storage 73 | mountPath: /data 74 | imagePullSecrets: 75 | - name: {{ .Values.common.secretName }} 76 | volumes: 77 | - emptyDir: 78 | medium: "" 79 | name: "storage" 80 | -------------------------------------------------------------------------------- /helm-charts/es/templates/es-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{template "fullname" .}} 5 | namespace: {{ .Values.common.namespace }} 6 | labels: 7 | component: {{template "fullname" .}} 8 | role: client 9 | spec: 10 | type: {{ .Values.elasticsearch.service.type }} 11 | selector: 12 | component: {{template "fullname" .}} 13 | role: client 14 | ports: 15 | - name: http 16 | port: {{ .Values.elasticsearch.service.httpPort }} 17 | targetPort: 9200 18 | protocol: TCP 19 | - name: transport 20 | port: {{ .Values.elasticsearch.service.transportPort }} 21 | targetPort: 9300 22 | protocol: TCP 23 | -------------------------------------------------------------------------------- /helm-charts/es/values.yaml: -------------------------------------------------------------------------------- 1 | common: 2 | namespace: elk-cluster-ns 3 | registry: elkacr.azurecr.io 4 | secretName: azure-registry 5 | env: 6 | CLUSTER_NAME: "elk-kube-acs-cluster" 7 | # Uncomment this if you get the "No up-and-running site-local (private) addresses" error 8 | # NETWORK_HOST: "_eth0_" 9 | 10 | nameOverride: elasticsearch 11 | elasticsearch: 12 | image: 13 | repository: elkacr.azurecr.io/elasticsearch 14 | tag: 1.0.0 15 | pullPolicy: Always 16 | # Client/ingest nodes can execute pre-processing pipelines, composed of 17 | # one or more ingest processors. Depending on the type of operations 18 | # performed by the ingest processors and the required resources, it may make 19 | # sense to have dedicated ingest nodes, that will only perform this specific task. 20 | client: 21 | # It isn't common to need more than 2 client nodes. 22 | replicas: 2 23 | 24 | env: 25 | NODE_DATA: "false" 26 | NODE_MASTER: "false" 27 | NODE_INGEST: "true" 28 | HTTP_ENABLE: "true" 29 | ES_JAVA_OPTS: "-Xms256m -Xmx256m" 30 | 31 | # Data nodes hold the shards that contain the documents you have indexed. Data 32 | # nodes handle data related operations like CRUD, search, and aggregations. 33 | # These operations are I/O-, memory-, and CPU-intensive. It is important to 34 | # monitor these resources and to add more data nodes if they are overloaded. 35 | # 36 | # The main benefit of having dedicated data nodes is the separation of the 37 | # master and data roles. 38 | data: 39 | # This count will depend on your data and computation needs. 40 | replicas: 2 41 | storageAccount: azdisksa 42 | location: westus 43 | storageSku: Standard_LRS 44 | env: 45 | NODE_DATA: "true" 46 | NODE_MASTER: "false" 47 | NODE_INGEST: "false" 48 | HTTP_ENABLE: "false" 49 | ES_JAVA_OPTS: "-Xms256m -Xmx256m" 50 | 51 | # The master node is responsible for lightweight cluster-wide actions such as 52 | # creating or deleting an index, tracking which nodes are part of the 53 | # cluster, and deciding which shards to allocate to which nodes. It is 54 | # important for cluster health to have a stable master node. 55 | master: 56 | # Master replica count should be (#clients / 2) + 1, and generally at least 3. 57 | replicas: 3 58 | env: 59 | NODE_DATA: "false" 60 | NODE_MASTER: "true" 61 | NODE_INGEST: "false" 62 | HTTP_ENABLE: "false" 63 | ES_JAVA_OPTS: "-Xms256m -Xmx256m" 64 | 65 | # The default value for this environment variable is 2, meaning a cluster 66 | # will need a minimum of 2 master nodes to operate. If you have 3 masters 67 | # and one dies, the cluster still works. 68 | NUMBER_OF_MASTERS: "2" 69 | 70 | service: 71 | # change it to LoadBalancer for public accessibility 72 | type: ClusterIP 73 | httpPort: 9200 74 | transportPort: 9300 75 | -------------------------------------------------------------------------------- /helm-charts/filebeat/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /helm-charts/filebeat/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: helm chart for filebeat 3 | name: filebeat 4 | version: 0.0.1 5 | maintainers: 6 | - name: Yawei Wang 7 | email: yaweiw@microsoft.com -------------------------------------------------------------------------------- /helm-charts/filebeat/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | */}} 13 | {{- define "fullname" -}} 14 | {{- default .Chart.Name .Values.nameOverride | trunc 53 | trimSuffix "-" -}} 15 | {{- end -}} 16 | -------------------------------------------------------------------------------- /helm-charts/filebeat/templates/filebeat-daemonset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: DaemonSet 3 | metadata: 4 | name: filebeat 5 | namespace: elk-cluster-ns 6 | labels: 7 | component: filebeat 8 | spec: 9 | template: 10 | metadata: 11 | labels: 12 | component: filebeat 13 | name: filebeat 14 | spec: 15 | containers: 16 | - name: filebeat 17 | image: "{{ .Values.common.registry }}/filebeat:{{ .Values.filebeat.image.tag }}" 18 | imagePullPolicy: {{ .Values.filebeat.image.pullPolicy }} 19 | resources: 20 | limits: 21 | cpu: 50m 22 | memory: 50Mi 23 | env: 24 | - name: LOGSTASH_HOSTS 25 | value: logstash:5043 26 | - name: LOG_LEVEL 27 | value: info 28 | # This pulls HOSTNAME from the node, not the pod 29 | - name: FILEBEAT_HOST 30 | valueFrom: 31 | fieldRef: 32 | fieldPath: spec.nodeName 33 | volumeMounts: 34 | - name: varlog 35 | mountPath: /var/log 36 | - name: varlogcontainers 37 | mountPath: /var/log/containers 38 | - name: varlogpods 39 | mountPath: /var/log/pods 40 | readOnly: true 41 | - name: varlibdockercontainers 42 | mountPath: /var/lib/docker/containers 43 | readOnly: true 44 | terminationGracePeriodSeconds: 30 45 | tolerations: 46 | - key: node-role.kubernetes.io/master 47 | effect: NoSchedule 48 | volumes: 49 | - name: varlog 50 | hostPath: 51 | path: /var/log 52 | - name: varlogcontainers 53 | hostPath: 54 | path: /var/log/containers 55 | - name: varlogpods 56 | hostPath: 57 | path: /var/log/pods 58 | - name: varlibdockercontainers 59 | hostPath: 60 | path: /var/lib/docker/containers -------------------------------------------------------------------------------- /helm-charts/filebeat/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for filebeat. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | common: 5 | namespace: elk-cluster-ns 6 | registry: elkacr.azurecr.io 7 | secretName: azure-registry 8 | nameOverride: filebeat 9 | filebeat: 10 | image: 11 | tag: 1.0.0 12 | pullPolicy: Always 13 | service: 14 | name: filebeat 15 | -------------------------------------------------------------------------------- /helm-charts/kibana/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /helm-charts/kibana/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: helm chart for kibana 3 | name: kibana 4 | version: 0.0.1 5 | maintainers: 6 | - name: Yawei Wang 7 | email: yaweiw@microsoft.com -------------------------------------------------------------------------------- /helm-charts/kibana/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | */}} 13 | {{- define "fullname" -}} 14 | {{- default .Chart.Name .Values.nameOverride | trunc 53 | trimSuffix "-" -}} 15 | {{- end -}} 16 | -------------------------------------------------------------------------------- /helm-charts/kibana/templates/kibana-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | name: {{ template "fullname" . }} 5 | namespace: {{ .Values.common.namespace }} 6 | labels: 7 | component: {{template "fullname" .}} 8 | spec: 9 | replicas: {{ .Values.kibana.replicaCount }} 10 | template: 11 | metadata: 12 | labels: 13 | component: {{ template "fullname" . }} 14 | spec: 15 | containers: 16 | - name: {{ .Chart.Name }} 17 | image: "{{ .Values.common.registry }}/kibana:{{ .Values.kibana.image.tag }}" 18 | imagePullPolicy: {{ .Values.kibana.image.pullPolicy }} 19 | env: 20 | {{- range $key, $value := .Values.kibana.env }} 21 | - name: {{ $key | upper | replace "-" "_" }} 22 | value: {{ $value | quote }} 23 | {{- end }} 24 | ports: 25 | - containerPort: {{ .Values.kibana.service.port }} 26 | imagePullSecrets: 27 | - name: {{ .Values.common.secretName }} 28 | -------------------------------------------------------------------------------- /helm-charts/kibana/templates/kibana-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ template "fullname" . }} 5 | namespace: {{ .Values.common.namespace }} 6 | labels: 7 | component: {{template "fullname" .}} 8 | spec: 9 | type: {{ .Values.kibana.service.type }} 10 | ports: 11 | - port: {{ .Values.kibana.service.port }} 12 | protocol: TCP 13 | name: {{ .Values.kibana.service.name }} 14 | selector: 15 | component: {{ template "fullname" . }} 16 | -------------------------------------------------------------------------------- /helm-charts/kibana/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for kibana. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | common: 5 | namespace: elk-cluster-ns 6 | secretName: azure-registry 7 | registry: elkacr.azurecr.io 8 | nameOverride: kibana 9 | kibana: 10 | replicaCount: 2 11 | image: 12 | tag: 1.0.0 13 | pullPolicy: Always 14 | service: 15 | name: kibana 16 | type: LoadBalancer 17 | port: 80 18 | env: 19 | USERNAME: azureuser 20 | PASSWORD: azureuser 21 | -------------------------------------------------------------------------------- /helm-charts/logstash/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /helm-charts/logstash/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: helm chart for logstash 3 | name: logstash 4 | version: 0.0.1 5 | maintainers: 6 | - name: Yawei Wang 7 | email: yaweiw@microsoft.com -------------------------------------------------------------------------------- /helm-charts/logstash/config/logstash.conf: -------------------------------------------------------------------------------- 1 | input { 2 | beats { 3 | host => "0.0.0.0" 4 | port => 5043 5 | } 6 | } 7 | 8 | output { 9 | elasticsearch { 10 | hosts => [ "${ELASTICSEARCH_URL}" ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /helm-charts/logstash/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | */}} 13 | {{- define "fullname" -}} 14 | {{- default .Chart.Name .Values.nameOverride | trunc 53 | trimSuffix "-" -}} 15 | {{- end -}} 16 | -------------------------------------------------------------------------------- /helm-charts/logstash/templates/logstash-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | logstash.conf: | 4 | {{ .Files.Get "config/logstash.conf" | printf "%s" | indent 4 }} 5 | kind: ConfigMap 6 | metadata: 7 | name: {{ template "fullname" . }} 8 | namespace: {{ .Values.common.namespace }} 9 | 10 | -------------------------------------------------------------------------------- /helm-charts/logstash/templates/logstash-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | name: {{ template "fullname" . }} 5 | namespace: {{ .Values.common.namespace }} 6 | labels: 7 | component: {{template "fullname" .}} 8 | spec: 9 | replicas: {{ .Values.logstash.replicaCount }} 10 | template: 11 | metadata: 12 | labels: 13 | component: {{ template "fullname" . }} 14 | spec: 15 | containers: 16 | - name: {{ .Chart.Name }} 17 | image: "{{ .Values.common.registry }}/logstash:{{ .Values.logstash.image.tag }}" 18 | imagePullPolicy: {{ .Values.logstash.image.pullPolicy }} 19 | ports: 20 | - containerPort: {{ .Values.logstash.service.port }} 21 | volumeMounts: 22 | - mountPath: {{ .Values.logstash.configuration.path }} 23 | name: config 24 | imagePullSecrets: 25 | - name: {{ .Values.common.secretName }} 26 | volumes: 27 | - name: config 28 | configMap: 29 | name: {{ template "fullname" . }} 30 | items: 31 | - key: logstash.conf 32 | path: logstash.conf -------------------------------------------------------------------------------- /helm-charts/logstash/templates/logstash-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ template "fullname" . }} 5 | namespace: {{ .Values.common.namespace }} 6 | labels: 7 | component: {{template "fullname" .}} 8 | spec: 9 | type: {{ .Values.logstash.service.type }} 10 | ports: 11 | - port: {{ .Values.logstash.service.port }} 12 | protocol: TCP 13 | name: {{ .Values.logstash.service.name }} 14 | selector: 15 | component: {{ template "fullname" . }} 16 | -------------------------------------------------------------------------------- /helm-charts/logstash/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for logstash. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | common: 5 | namespace: elk-cluster-ns 6 | secretName: azure-registry 7 | nameOverride: logstash 8 | logstash: 9 | replicaCount: 2 10 | image: 11 | repository: elkacr.azurecr.io/logstash 12 | tag: 1.0.0 13 | pullPolicy: Always 14 | service: 15 | name: logstash 16 | type: LoadBalancer 17 | port: 5043 18 | configuration: 19 | path: /conf 20 | -------------------------------------------------------------------------------- /helm-charts/ns/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: helm chart for elk cluster namespace 3 | name: elknamespace 4 | version: 0.0.1 5 | maintainers: 6 | - name: Yawei Wang 7 | email: yaweiw@microsoft.com 8 | engine: gotpl 9 | -------------------------------------------------------------------------------- /helm-charts/ns/templates/elk-namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: {{ .Values.common.namespace }} 5 | labels: 6 | name: {{ .Values.common.namespace }} -------------------------------------------------------------------------------- /helm-charts/ns/values.yaml: -------------------------------------------------------------------------------- 1 | common: 2 | namespace: elk-cluster-ns -------------------------------------------------------------------------------- /helm-charts/start-elk.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # terminate once a command failed 4 | set -e 5 | 6 | echo $@ 7 | 8 | while getopts ':r:u:p:d:l:s:a:b:' arg 9 | do 10 | case ${arg} in 11 | r) registryUrl=${OPTARG};; 12 | u) registryUsername=${OPTARG};; 13 | p) registryPassword=${OPTARG};; 14 | d) storageAccountName=${OPTARG};; 15 | l) resourceLocation=${OPTARG};; 16 | s) storageAccountSku=${OPTARG};; 17 | a) kibanaUsername=${OPTARG};; 18 | b) kibanaPassword=${OPTARG};; 19 | esac 20 | done 21 | 22 | export TAG='latest' 23 | export REGISTRY_URL=${registryUrl} 24 | export STORAGE_ACCOUNT=${storageAccountName} 25 | export STORAGE_LOCATION=${resourceLocation} 26 | export STORAGE_SKU=${storageAccountSku} 27 | export NAMESPACE=elk-cluster-ns 28 | export USERNAME=${kibanaUsername} 29 | export PASSWORD=${kibanaPassword} 30 | 31 | # substitute environment variables 32 | cat config.yaml | envsubst > effect.yaml 33 | 34 | helm install -f effect.yaml ns 35 | 36 | echo ${registryUsername} 37 | if [ ! -z ${registryUsername} ]; then 38 | # create secret 39 | registry_name=azure-registry 40 | registry_email=example@example.com 41 | 42 | kubectl --namespace=${NAMESPACE} create secret docker-registry ${registry_name} \ 43 | --docker-server=${registryUrl} \ 44 | --docker-username=${registryUsername} \ 45 | --docker-password=${registryPassword} \ 46 | --docker-email=${registry_email} 47 | fi 48 | 49 | # create Elasticsearch 50 | helm install -f effect.yaml es 51 | 52 | # create Kibana 53 | helm install -f effect.yaml kibana 54 | 55 | # create Logstash 56 | helm install -f effect.yaml logstash -------------------------------------------------------------------------------- /image/elk-acs-kube-aad-access.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/elk-acs-kubernetes/a2dc458759e71383eede052d3db0753a5d8465fb/image/elk-acs-kube-aad-access.png -------------------------------------------------------------------------------- /image/elk-acs-kube-aad-redirect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/elk-acs-kubernetes/a2dc458759e71383eede052d3db0753a5d8465fb/image/elk-acs-kube-aad-redirect.png -------------------------------------------------------------------------------- /image/elk-acs-kube-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/elk-acs-kubernetes/a2dc458759e71383eede052d3db0753a5d8465fb/image/elk-acs-kube-arch.png -------------------------------------------------------------------------------- /mainTemplate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "dnsNamePrefix": { 6 | "type": "string", 7 | "metadata": { 8 | "description": "Sets the Domain name prefix for the cluster. The concatenation of the domain name and the regionalized DNS zone make up the fully qualified domain name associated with the public IP address." 9 | } 10 | }, 11 | "agentCount": { 12 | "type": "int", 13 | "defaultValue": 1, 14 | "metadata": { 15 | "description": "The number of agents for the cluster. This value can be from 1 to 100 (note, for Kubernetes clusters you will also get 1 or 2 public agents in addition to these seleted masters)" 16 | }, 17 | "minValue": 1, 18 | "maxValue": 100 19 | }, 20 | "agentVMSize": { 21 | "type": "string", 22 | "defaultValue": "Standard_DS3_v2", 23 | "allowedValues": [ 24 | "Standard_A0", 25 | "Standard_A1", 26 | "Standard_A2", 27 | "Standard_A3", 28 | "Standard_A4", 29 | "Standard_A1_v2", 30 | "Standard_A2_v2", 31 | "Standard_A4_v2", 32 | "Standard_A8_v2", 33 | "Standard_A2m_v2", 34 | "Standard_A4m_v2", 35 | "Standard_A8m_v2", 36 | "Standard_D2", 37 | "Standard_D3", 38 | "Standard_D4", 39 | "Standard_D11", 40 | "Standard_D12", 41 | "Standard_D13", 42 | "Standard_D14", 43 | "Standard_D2_v2", 44 | "Standard_D3_v2", 45 | "Standard_D4_v2", 46 | "Standard_D5_v2", 47 | "Standard_D11_v2", 48 | "Standard_D12_v2", 49 | "Standard_D13_v2", 50 | "Standard_D14_v2", 51 | "Standard_DS2", 52 | "Standard_DS3", 53 | "Standard_DS4", 54 | "Standard_DS11", 55 | "Standard_DS12", 56 | "Standard_DS13", 57 | "Standard_DS14", 58 | "Standard_DS2_v2", 59 | "Standard_DS3_v2", 60 | "Standard_DS4_v2", 61 | "Standard_DS5_v2", 62 | "Standard_DS11_v2", 63 | "Standard_DS12_v2", 64 | "Standard_DS13_v2", 65 | "Standard_DS14_v2", 66 | "Standard_G1", 67 | "Standard_G2", 68 | "Standard_G3", 69 | "Standard_G4", 70 | "Standard_G5", 71 | "Standard_GS1", 72 | "Standard_GS2", 73 | "Standard_GS3", 74 | "Standard_GS4", 75 | "Standard_GS5" 76 | ], 77 | "metadata": { 78 | "description": "The size of the Virtual Machine as agent." 79 | } 80 | }, 81 | "linuxAdminUsername": { 82 | "type": "string", 83 | "defaultValue": "azureuser", 84 | "metadata": { 85 | "description": "User name for the Linux Virtual Machines." 86 | } 87 | }, 88 | "masterCount": { 89 | "type": "int", 90 | "defaultValue": 1, 91 | "allowedValues": [ 92 | 1, 93 | 3, 94 | 5 95 | ], 96 | "metadata": { 97 | "description": "The number of Kubernetes masters for the cluster." 98 | } 99 | }, 100 | "sshRSAPublicKey": { 101 | "type": "string", 102 | "metadata": { 103 | "description": "Configure all linux machines with the SSH RSA public key string. Your key should include three parts, for example 'ssh-rsa AAAAB...snip...UcyupgH azureuser@linuxvm'" 104 | } 105 | }, 106 | "servicePrincipalClientId": { 107 | "metadata": { 108 | "description": "Client ID (used by cloudprovider)" 109 | }, 110 | "type": "securestring" 111 | }, 112 | "servicePrincipalClientSecret": { 113 | "metadata": { 114 | "description": "The Service Principal Client Secret." 115 | }, 116 | "type": "securestring" 117 | }, 118 | "adminPassword": { 119 | "type": "securestring", 120 | "metadata": { 121 | "description": "Password to login controller node" 122 | } 123 | }, 124 | "ubuntuOSVersion": { 125 | "type": "string", 126 | "defaultValue": "16.04.0-LTS", 127 | "allowedValues": [ 128 | "12.04.5-LTS", 129 | "14.04.5-LTS", 130 | "16.04.0-LTS" 131 | ], 132 | "metadata": { 133 | "description": "The Ubuntu version for the controller node. This will pick a fully patched image of this given Ubuntu version." 134 | } 135 | }, 136 | "privateKey": { 137 | "type": "securestring", 138 | "metadata": { 139 | "description": "Base64 encoded private key corresbonding to the public key for ACS" 140 | } 141 | }, 142 | "location": { 143 | "type": "string", 144 | "defaultValue": "[resourceGroup().location]", 145 | "metadata": { 146 | "description": "The location of all components" 147 | } 148 | }, 149 | "registryUrl": { 150 | "type": "string", 151 | "defaultValue": "", 152 | "metadata": { 153 | "description": "Custom registry(e.g. Docker Hub) url to be used" 154 | } 155 | }, 156 | "eventHubNamespace": { 157 | "type": "string", 158 | "defaultValue": "undefined", 159 | "metadata": { 160 | "description": "The target event hub namespace" 161 | } 162 | }, 163 | "eventHubKeyName": { 164 | "type": "string", 165 | "defaultValue": "undefined", 166 | "metadata": { 167 | "description": "The name of the shared access policy" 168 | } 169 | }, 170 | "eventHubKey": { 171 | "type": "string", 172 | "defaultValue": "undefined", 173 | "metadata": { 174 | "description": "The shared access key to the target event hub" 175 | } 176 | }, 177 | "eventHubEntityPath": { 178 | "type": "string", 179 | "defaultValue": "undefined", 180 | "metadata": { 181 | "description": "The target event hub name" 182 | } 183 | }, 184 | "eventHubPartitionnumber": { 185 | "type": "string", 186 | "defaultValue": "4", 187 | "metadata": { 188 | "description": "Partition count of the target event hub" 189 | } 190 | }, 191 | "eventHubThreadWaitSec": { 192 | "type": "string", 193 | "defaultValue": "10", 194 | "metadata": { 195 | "description": "Logstash event hub plugin thread wait interval in seconds" 196 | } 197 | }, 198 | "storageAccountSku": { 199 | "type": "string", 200 | "defaultValue": "Standard_LRS", 201 | "allowedValues": [ 202 | "Standard_LRS", 203 | "Standard_GRS", 204 | "Standard_RAGRS", 205 | "Standard_ZRS", 206 | "Premium_LRS" 207 | ], 208 | "metadata": { 209 | "description": "Storage account sku to be used as Elasticsearch data node" 210 | } 211 | }, 212 | "authenticationMode": { 213 | "type": "string", 214 | "defaultValue": "BasicAuth", 215 | "allowedValues": [ 216 | "BasicAuth", 217 | "AzureAD" 218 | ], 219 | "metadata": { 220 | "description": "User authentication mode." 221 | } 222 | }, 223 | "azureAdClientId": { 224 | "type": "string", 225 | "defaultValue": "undefined", 226 | "metadata": { 227 | "description": "Azure AD client ID(Application ID)" 228 | } 229 | }, 230 | "azureAdClientSecret": { 231 | "type": "securestring", 232 | "defaultValue": "undefined", 233 | "metadata": { 234 | "description": "Azure AD client secret/key" 235 | } 236 | }, 237 | "tenant": { 238 | "type": "string", 239 | "defaultValue": "undefined", 240 | "metadata": { 241 | "description": "Azure AD tenant(e.g. contoso.onmicrosoft.com)" 242 | } 243 | } 244 | }, 245 | "variables": { 246 | "baseTemplateUrl": "[uri(deployment().properties.templateLink.uri, '.')]", 247 | "archiveUrl": "[uri(deployment().properties.templateLink.uri, 'solution.zip')]", 248 | "directoryName": "", 249 | "adminUsername": "[parameters('linuxAdminUsername')]", 250 | "agentCount": "[parameters('agentCount')]", 251 | "agentsEndpointDNSNamePrefix": "[concat(parameters('dnsNamePrefix'), 'agents')]", 252 | "agentVMSize": "[parameters('agentVMSize')]", 253 | "masterCount": "[parameters('masterCount')]", 254 | "mastersEndpointDNSNamePrefix": "[concat(parameters('dnsNamePrefix'), 'master')]", 255 | "orchestratorType": "Kubernetes", 256 | "sshRSAPublicKey": "[parameters('sshRSAPublicKey')]", 257 | "servicePrincipalClientId": "[parameters('servicePrincipalClientId')]", 258 | "servicePrincipalClientSecret": "[parameters('servicePrincipalClientSecret')]", 259 | "controllerDNSNamePrefix": "[concat(parameters('dnsNamePrefix'), 'control')]", 260 | "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'ctl')]", 261 | "frontEndNSGName": "[concat(uniquestring(resourceGroup().id), 'frontEndNSG')]", 262 | "imagePublisher": "Canonical", 263 | "imageOffer": "UbuntuServer", 264 | "nicName": "controllernic", 265 | "addressPrefix": "10.0.0.0/16", 266 | "subnetName": "Subnet", 267 | "subnetPrefix": "10.0.0.0/24", 268 | "storageAccountType": "Standard_LRS", 269 | "publicIPAddressName": "controllerip", 270 | "publicIPAddressType": "Dynamic", 271 | "vmName": "controllervm", 272 | "vmSize": "Standard_A1", 273 | "virtualNetworkName": "controller-vnet", 274 | "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]", 275 | "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", 276 | "dataStorageAccountName": "[concat(uniquestring(resourceGroup().id), 'data')]", 277 | "registryName": "[concat(uniquestring(resourceGroup().id), 'registry')]", 278 | "registryDisk": "[concat(uniquestring(resourceGroup().id), 'elkimage')]", 279 | "basedPrivateKey": "[parameters('privateKey')]", 280 | "baseUrl": "[concat(variables('baseTemplateUrl'), '/ARM-template/components/')]", 281 | "registryTemplates": { 282 | "True": "containerRegistry.json", 283 | "False": "emptyTemplate.json" 284 | }, 285 | "actualTemplate": "[concat(variables('baseUrl'), variables('registryTemplates')[string(empty(parameters('registryUrl')))])]" 286 | }, 287 | "resources": [ 288 | { 289 | "apiVersion": "2016-09-01", 290 | "name": "containerService", 291 | "type": "Microsoft.Resources/deployments", 292 | "properties": { 293 | "mode": "Incremental", 294 | "templateLink": { 295 | "uri": "[concat(variables('baseUrl'), 'containerService.json')]", 296 | "contentVersion": "1.0.0.0" 297 | }, 298 | "parameters": { 299 | "dnsNamePrefix": { 300 | "value": "[parameters('dnsNamePrefix')]" 301 | }, 302 | "agentCount": { 303 | "value": "[parameters('agentCount')]" 304 | }, 305 | "agentVMSize": { 306 | "value": "[parameters('agentVMSize')]" 307 | }, 308 | "linuxAdminUsername": { 309 | "value": "[parameters('linuxAdminUsername')]" 310 | }, 311 | "masterCount": { 312 | "value": "[parameters('masterCount')]" 313 | }, 314 | "sshRSAPublicKey": { 315 | "value": "[parameters('sshRSAPublicKey')]" 316 | }, 317 | "servicePrincipalClientId": { 318 | "value": "[parameters('servicePrincipalClientId')]" 319 | }, 320 | "servicePrincipalClientSecret": { 321 | "value": "[parameters('servicePrincipalClientSecret')]" 322 | }, 323 | "location": { 324 | "value": "[parameters('location')]" 325 | } 326 | } 327 | } 328 | }, 329 | { 330 | "apiVersion": "2016-09-01", 331 | "name": "storageAccount", 332 | "type": "Microsoft.Resources/deployments", 333 | "properties": { 334 | "mode": "Incremental", 335 | "templateLink": { 336 | "uri": "[concat(variables('baseUrl'), 'storageAccount.json')]", 337 | "contentVersion": "1.0.0.0" 338 | }, 339 | "parameters": { 340 | "storageAccountSku": { 341 | "value": "[parameters('storageAccountSku')]" 342 | }, 343 | "location": { 344 | "value": "[parameters('location')]" 345 | } 346 | } 347 | } 348 | }, 349 | { 350 | "apiVersion": "2016-09-01", 351 | "name": "containerRegistry", 352 | "type": "Microsoft.Resources/deployments", 353 | "properties": { 354 | "mode": "Incremental", 355 | "templateLink": { 356 | "uri": "[variables('actualTemplate')]", 357 | "contentVersion": "1.0.0.0" 358 | }, 359 | "parameters": { 360 | "location": { 361 | "value": "[parameters('location')]" 362 | } 363 | } 364 | } 365 | }, 366 | { 367 | "apiVersion": "2016-09-01", 368 | "name": "controllerNode", 369 | "type": "Microsoft.Resources/deployments", 370 | "dependsOn": [ 371 | "[resourceId('Microsoft.Resources/deployments/', 'containerService')]", 372 | "[resourceId('Microsoft.Resources/deployments/', 'storageAccount')]", 373 | "[resourceId('Microsoft.Resources/deployments/', 'containerRegistry')]" 374 | ], 375 | "properties": { 376 | "mode": "Incremental", 377 | "templateLink": { 378 | "uri": "[concat(variables('baseUrl'), 'controllerNode.json')]", 379 | "contentVersion": "1.0.0.0" 380 | }, 381 | "parameters": { 382 | "dnsNamePrefix": { 383 | "value": "[parameters('dnsNamePrefix')]" 384 | }, 385 | "linuxAdminUsername": { 386 | "value": "[parameters('linuxAdminUsername')]" 387 | }, 388 | "adminPassword": { 389 | "value": "[parameters('adminPassword')]" 390 | }, 391 | "ubuntuOSVersion": { 392 | "value": "[parameters('ubuntuOSVersion')]" 393 | }, 394 | "privateKey": { 395 | "value": "[parameters('privateKey')]" 396 | }, 397 | "location": { 398 | "value": "[parameters('location')]" 399 | }, 400 | "registryUrl": { 401 | "value": "[parameters('registryUrl')]" 402 | }, 403 | "eventHubNamespace": { 404 | "value": "[parameters('eventHubNamespace')]" 405 | }, 406 | "eventHubKeyName": { 407 | "value": "[parameters('eventHubKeyName')]" 408 | }, 409 | "eventHubKey": { 410 | "value": "[parameters('eventHubKey')]" 411 | }, 412 | "eventHubEntityPath": { 413 | "value": "[parameters('eventHubEntityPath')]" 414 | }, 415 | "eventHubPartitionnumber": { 416 | "value": "[parameters('eventHubPartitionnumber')]" 417 | }, 418 | "eventHubThreadWaitSec": { 419 | "value": "[parameters('eventHubThreadWaitSec')]" 420 | }, 421 | "baseTemplateUrl": { 422 | "value": "[variables('baseTemplateUrl')]" 423 | }, 424 | "storageAccountSku": { 425 | "value": "[parameters('storageAccountSku')]" 426 | }, 427 | "archiveUrl": { 428 | "value": "[variables('archiveUrl')]" 429 | }, 430 | "directoryName": { 431 | "value": "[variables('directoryName')]" 432 | }, 433 | "authenticationMode": { 434 | "value": "[parameters('authenticationMode')]" 435 | }, 436 | "azureAdClientId": { 437 | "value": "[parameters('azureAdClientId')]" 438 | }, 439 | "azureAdClientSecret": { 440 | "value": "[parameters('azureAdClientSecret')]" 441 | }, 442 | "tenant": { 443 | "value": "[parameters('tenant')]" 444 | }, 445 | "servicePrincipalClientId": { 446 | "value": "[parameters('servicePrincipalClientId')]" 447 | }, 448 | "servicePrincipalClientSecret": { 449 | "value": "[parameters('servicePrincipalClientSecret')]" 450 | } 451 | } 452 | } 453 | } 454 | ], 455 | "outputs": { 456 | "hostname": { 457 | "type": "string", 458 | "value": "[reference('controllerNode').outputs.hostname.value]" 459 | }, 460 | "sshCommand": { 461 | "type": "string", 462 | "value": "[reference('controllerNode').outputs.sshCommand.value]" 463 | } 464 | } 465 | } -------------------------------------------------------------------------------- /scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | log() 4 | { 5 | echo "$1" | tee -a /tmp/output.log 6 | } 7 | 8 | set -e 9 | 10 | echo $@ 11 | 12 | log "enter scripts/run.sh" 13 | while getopts ':d:l:u:p:k:r:a:b:j:q:m:n:o:v:s:c:e:f:g:h:i:t:w:x:y:z:R:' arg; do 14 | log "arg $arg set with value ${OPTARG}" 15 | case ${arg} in 16 | d) masterDns=${OPTARG};; 17 | l) resourceLocation=${OPTARG};; 18 | u) masterUsername=${OPTARG};; 19 | p) masterPassword=${OPTARG};; 20 | k) privateKey=${OPTARG};; 21 | r) registryUrl=${OPTARG};; 22 | a) registryUsername=${OPTARG};; 23 | b) registryPassword=${OPTARG};; 24 | j) diagEvtHubNs=${OPTARG};; # for logstash eh plugin 25 | q) diagEvtHubNa=${OPTARG};; # for logstash eh plugin 26 | m) diagEvtHubKey=${OPTARG};; # for logstash eh plugin 27 | n) diagEvtHubEntPa=${OPTARG};; # for logstash eh plugin 28 | o) diagEvtHubPartNum=${OPTARG};; # for logstash eh plugin 29 | v) diagEvtHubThreadWait=${OPTARG};; # for logstash eh plugin 30 | s) storageAccount=${OPTARG};; 31 | c) storageAccountSku=${OPTARG};; 32 | e) repositoryUrl=${OPTARG};; 33 | f) directoryName=${OPTARG};; 34 | g) authenticationMode=${OPTARG};; # "AzureAD" or "BasicAuth" 35 | h) clientId=${OPTARG};; 36 | i) clientSecret=${OPTARG};; 37 | t) tenant=${OPTARG};; 38 | w) azureCliendId=${OPTARG};; 39 | x) azurePwd=${OPTARG};; 40 | y) azureTenant=${OPTARG};; 41 | z) subscriptionId=${OPTARG};; 42 | R) resourceGroup=${OPTARG};; 43 | esac 44 | done 45 | 46 | if [ -z ${masterDns} ]; then 47 | echo 'Master DNS is required' >&2 48 | exit 1 49 | fi 50 | 51 | if [ -z ${resourceLocation} ]; then 52 | echo 'Resource location is required' >&2 53 | exit 1 54 | fi 55 | 56 | if [ -z ${masterUsername} ]; then 57 | echo 'Master username is required' >&2 58 | exit 1 59 | fi 60 | 61 | if [ -z ${masterPassword} ]; then 62 | echo 'Master password is required' >&2 63 | exit 1 64 | fi 65 | 66 | if [ -z ${privateKey} ]; then 67 | echo 'Private key is required' >&2 68 | exit 1 69 | fi 70 | 71 | if [ -z ${storageAccount} ]; then 72 | echo 'Storage account name is required' >&2 73 | exit 1 74 | fi 75 | 76 | if [ -z ${storageAccountSku} ]; then 77 | echo 'Storage account sku is required' >&2 78 | exit 1 79 | fi 80 | 81 | if [ -z ${repositoryUrl} ]; then 82 | echo 'Repository URL is required' >&2 83 | exit 1 84 | fi 85 | 86 | if [ -z ${azureCliendId} ]; then 87 | echo 'Azure AD client id is required' >&2 88 | exit 1 89 | fi 90 | 91 | if [ -z ${azurePwd} ]; then 92 | echo 'Azure AD password is required' >&2 93 | exit 1 94 | fi 95 | 96 | if [ -z ${azureTenant} ]; then 97 | echo 'Azure AD tenand id is required' >&2 98 | exit 1 99 | fi 100 | 101 | if [ -z ${authenticationMode} ]; then 102 | authenticationMode = 'BasicAuth' 103 | fi 104 | 105 | if [ "${authenticationMode}" = "AzureAD" ]; then 106 | if [ -z ${clientId} ]; then 107 | echo 'Client ID is required in Azure AD mode' >&2 108 | exit 1 109 | fi 110 | if [ -z ${clientSecret} ]; then 111 | echo 'Client secret is required in Azure AD mode' >&2 112 | exit 1 113 | fi 114 | if [ -z ${tenant} ]; then 115 | echo 'Tenant is required in Azure AD mode' >&2 116 | exit 1 117 | fi 118 | fi 119 | 120 | log "===================" 121 | log " " 122 | log "Re-deploy solution using Azure Container Registry:" 123 | log "run.sh -d ${masterDns} -l ${resourceLocation} -u ${masterUsername} -p ${masterPassword} -k ${privateKey} -a ${registryUsername} -b ${registryPassword} -j ${diagEvtHubNs} -q ${diagEvtHubNa} -m ${diagEvtHubKey} -n ${diagEvtHubEntPa} -o ${diagEvtHubPartNum} -v ${diagEvtHubThreadWait} -s ${storageAccount} -c ${storageAccountSku} -e ${repositoryUrl} -f ${directoryName} -g ${authenticationMode} -h ${clientId} -i ${clientSecret} -t ${tenant} -w ${azureCliendId} -x ${azurePwd} -y ${azureTenant} -z ${subscriptionId} -R ${resourceGroup}" 124 | log " " 125 | log "Re-deploy solution using Custom Registry:" 126 | log "run.sh -d ${masterDns} -l ${resourceLocation} -u ${masterUsername} -p ${masterPassword} -k ${privateKey} -r ${registryUrl} -j ${diagEvtHubNs} -q ${diagEvtHubNa} -m ${diagEvtHubKey} -n ${diagEvtHubEntPa} -o ${diagEvtHubPartNum} -v ${diagEvtHubThreadWait} -s ${storageAccount} -c ${storageAccountSku} -e ${repositoryUrl} -f ${directoryName} -g ${authenticationMode} -h ${clientId} -i ${clientSecret} -t ${tenant} -w ${azureCliendId} -x ${azurePwd} -y ${azureTenant} -z ${subscriptionId} -R ${resourceGroup}" 127 | log " " 128 | log "===================" 129 | 130 | privateKeyFile='private_key' 131 | 132 | masterUrl=${masterDns}.${resourceLocation}.cloudapp.azure.com 133 | log "masterUrl: ${masterUrl}" 134 | 135 | export KUBECONFIG=/root/.kube/config 136 | export HOME=/root 137 | 138 | # prerequisites, e.g. docker, openresty 139 | echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ wheezy main" | sudo tee /etc/apt/sources.list.d/azure-cli.list 140 | sudo apt-key adv --keyserver packages.microsoft.com --recv-keys 52E16F86FEE04B979B07E28DB02C46DF417A0893 141 | sudo apt-get -y install apt-transport-https 142 | 143 | sudo apt-get -y install software-properties-common 144 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 145 | wget -qO - https://openresty.org/package/pubkey.gpg | sudo apt-key add - 146 | curl -L https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - 147 | sudo add-apt-repository -y "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" 148 | sudo add-apt-repository -y "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" 149 | sudo apt-get update 150 | apt-cache policy docker-ce 151 | sudo apt-get install -y unzip docker-ce openresty apache2-utils azure-cli 152 | 153 | log "Login Azure CLI" 154 | az login --service-principal -u ${azureCliendId} -p ${azurePwd} -t ${azureTenant} 155 | az account set -s ${subscriptionId} 156 | 157 | # Add NSG rule 158 | # virtualNetwork:22 to Any:* 159 | nsgs=$(az network nsg list -g ${resourceGroup} --output table) 160 | k8sMasterRegex='k8s-master-.*-nsg' 161 | if [[ ${nsgs} =~ ${k8sMasterRegex} ]]; 162 | then 163 | nsg=$(echo ${nsgs} | grep -o -e ${k8sMasterRegex}) 164 | ipAddress=$(az network public-ip show -n controllerip -g ${resourceGroup} --query [ipAddress] --output tsv) 165 | log "Add ${ipAddress} to ${nsg}" 166 | az network nsg rule create -n ssh --nsg-name ${nsg} --priority 102 -g ${resourceGroup} --protocol TCP --destination-port-range 22 --source-address-prefix ${ipAddress} 167 | else 168 | log "No k8s master security rule find: ${nsgs}" 169 | fi 170 | 171 | log "install kubectl" 172 | # install kubectl 173 | # az acs kubernetes install-cli 174 | cd /tmp 175 | # curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl 176 | # ACS currently support K8s v1.7.7 177 | curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.7.7/bin/linux/amd64/kubectl 178 | chmod +x ./kubectl 179 | sudo mv ./kubectl /usr/local/bin/kubectl 180 | 181 | 182 | log "write private key to ${privateKeyFile}" 183 | # write private key 184 | echo "${privateKey}" | base64 -d | tee ${privateKeyFile} 185 | chmod 400 ${privateKeyFile} 186 | mv ${privateKeyFile} ~/.ssh/id_rsa 187 | log "Download K8S credential file" 188 | az acs kubernetes get-credentials -n containerservice-${resourceGroup} -g ${resourceGroup} 189 | kubectl get nodes 190 | 191 | log "install helm" 192 | # install helm 193 | curl -L "https://kubernetes-helm.storage.googleapis.com/helm-v2.5.1-linux-amd64.tar.gz" -o helm.tar.gz 194 | tar vzxf helm.tar.gz 195 | cp linux-amd64/helm /usr/local/bin/helm 196 | helm init 197 | 198 | # make sure helm installed 199 | until [ $(kubectl get pods -n kube-system -l app=helm,name=tiller -o jsonpath="{.items[0].status.containerStatuses[0].ready}") = "true" ]; do 200 | sleep 2 201 | done 202 | 203 | log "download templates from ${repositoryUrl}" 204 | # download templates 205 | curl -L ${repositoryUrl} -o template.zip 206 | unzip -o template.zip -d template 207 | 208 | log "expose kubectl proxy at 8080" 209 | # expose kubectl proxy 210 | nohup kubectl proxy --port=8080 & 211 | 212 | log "config nginx for ${authenticationMode} authentication mode" 213 | cd template/${directoryName} 214 | if [ "${authenticationMode}" = "BasicAuth" ]; then 215 | echo ${masterPassword} | htpasswd -c -i /usr/local/openresty/nginx/conf/.htpasswd ${masterUsername} 216 | cp config/nginx-basic.conf /usr/local/openresty/nginx/conf/nginx.conf 217 | systemctl reload openresty 218 | else 219 | opm get pintsized/lua-resty-http bungle/lua-resty-session 220 | cp config/openidc.lua /usr/local/openresty/lualib/resty/openidc.lua 221 | export TENANT=${tenant} 222 | export CLIENT_ID=${clientId} 223 | export CLIENT_SECRET=${clientSecret} 224 | cat config/nginx-openid.conf | envsubst > /usr/local/openresty/nginx/conf/nginx.conf 225 | systemctl reload openresty 226 | fi 227 | 228 | # push image 229 | cd docker 230 | if [ -z ${registryUrl} ]; then 231 | log "push image to azure container registry: ${registryUsername}.azurecr.io" 232 | # assume azure container registry, image push is required. 233 | registryUrl=${registryUsername}.azurecr.io 234 | bash push-images.sh -r ${registryUrl} -u ${registryUsername} -p ${registryPassword} -a ${diagEvtHubNs} -b ${diagEvtHubNa} -c ${diagEvtHubKey} -d ${diagEvtHubEntPa} -e ${diagEvtHubPartNum} -f ${diagEvtHubThreadWait} 235 | 236 | cd ../helm-charts 237 | bash start-elk.sh -r ${registryUrl} -u ${registryUsername} -p ${registryPassword} -d ${storageAccount} -l ${resourceLocation} -s ${storageAccountSku} -a ${masterUsername} -b ${masterPassword} 238 | 239 | else 240 | log "install helm charts and start elk cluster" 241 | # install helm charts 242 | cd ../helm-charts 243 | bash start-elk.sh -r ${registryUrl} -d ${storageAccount} -l ${resourceLocation} -s ${storageAccountSku} -a ${masterUsername} -b ${masterPassword} 244 | 245 | fi 246 | log "exit scripts/run.sh" 247 | exit 0 248 | --------------------------------------------------------------------------------