├── .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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------