Skip to content

Commit 980d95c

Browse files
committed
Merge remote-tracking branch 'origin/develop'
2 parents 864affb + 4daef80 commit 980d95c

8 files changed

Lines changed: 109 additions & 62 deletions

File tree

docs/configure-rbac-permissions.md

Lines changed: 55 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,76 @@
11
# Configure RBAC permissions
22

3-
`Microsoft.ApplicationInsights.Kubernetes` uses the service account to query kubernetes information to enhance telemetries. It is important to have proper permissions configured for kubernetes related information like node, pod and so on to be fetched correctly.
3+
`Microsoft.ApplicationInsights.Kubernetes` uses the service account to query Kubernetes information to enhance telemetries. It is important to have proper permissions configured for Kubernetes-related resources like Node, Pod, and so on to be fetched correctly.
44

5-
In this post, we will start by describe a method to correctly configure the permissions for an RBAC enabled cluster. And then share a guidance for troubleshooting.
5+
In this post, we will start by describing a method to correctly configure the permissions for an RBAC-enabled cluster. And then share troubleshooting guidance.
66

77
## Assumptions
88

99
In this demo, we will have the following assumptions. Please change the related values accordingly:
1010

11-
* The application will be deployed to namespace of `ai-k8s-demo`.
11+
* The application will be deployed to namespace `ai-k8s-demo`.
1212
* The application will leverage the `default` service account.
1313

14-
## Configure ClusterRole and ClusterRoleBinding for the service account
15-
16-
* Create a yaml file, [sa-role.yaml](./sa-role.yaml) for example. We will deploy it when it is ready.
17-
18-
* Write spec to define a cluster role, name it `appinsights-k8s-property-reader` for example:
19-
20-
```yaml
21-
kind: ClusterRole
22-
apiVersion: rbac.authorization.k8s.io/v1
23-
metadata:
24-
# "namespace" omitted since ClusterRoles are not namespaced
25-
name: appinsights-k8s-property-reader
26-
rules:
27-
- apiGroups: ["", "apps"]
28-
resources: ["pods", "nodes", "replicasets", "deployments"]
29-
verbs: ["get", "list"]
30-
```
31-
That spec defines the name of the role, and what permission does the role has, for example, list pods.
32-
33-
You don't have to use the exact name, but you will need to making sure the name is referenced correctly in the following steps.
34-
35-
* Append a Cluster role binding spec:
36-
37-
```yaml
38-
---
39-
# actual binding to the role
40-
kind: ClusterRoleBinding
41-
apiVersion: rbac.authorization.k8s.io/v1
42-
metadata:
43-
name: appinsights-k8s-property-reader-binding
44-
subjects:
45-
- kind: ServiceAccount
46-
name: default
47-
namespace: ai-k8s-demo
48-
roleRef:
49-
kind: ClusterRole
50-
name: appinsights-k8s-property-reader
51-
apiGroup: rbac.authorization.k8s.io
52-
```
53-
54-
That is to grant the role of `appinsights-k8s-property-reader` to the default service account in namespace of `ai-k8s-demo`.
55-
14+
## Setup the permissions for the service account
15+
16+
Depending on various considerations, there could be different strategies to set up the permissions for your service account. Here we list 2 common possibilities, as examples.
17+
18+
* If you want to get the Node information along with other resource info like Pod, Deployment, and so on, a ClusterRole and a ClusterRoleBinding are required, and here's how to do it:
19+
20+
* Create a yaml file, [sa-role.yaml](./sa-role.yaml) for example. We will deploy it when it is ready.
21+
22+
* Write spec to define a cluster role, name it `appinsights-k8s-property-reader` for example:
23+
24+
```yaml
25+
kind: ClusterRole
26+
apiVersion: rbac.authorization.k8s.io/v1
27+
metadata:
28+
# "namespace" omitted since ClusterRoles are not namespaced
29+
name: appinsights-k8s-property-reader
30+
rules:
31+
- apiGroups: ["", "apps"]
32+
resources: ["pods", "nodes", "replicasets", "deployments"]
33+
verbs: ["get", "list"]
34+
```
35+
That spec defines the name of the role, and what permission does the role has, for example, list pods.
36+
37+
You don't have to use the exact name, but you will need to making sure the name is referenced correctly in the following steps.
38+
39+
* Append a Cluster role binding spec:
40+
41+
```yaml
42+
---
43+
# actual binding to the role
44+
kind: ClusterRoleBinding
45+
apiVersion: rbac.authorization.k8s.io/v1
46+
metadata:
47+
name: appinsights-k8s-property-reader-binding
48+
subjects:
49+
- kind: ServiceAccount
50+
name: default
51+
namespace: ai-k8s-demo
52+
roleRef:
53+
kind: ClusterRole
54+
name: appinsights-k8s-property-reader
55+
apiGroup: rbac.authorization.k8s.io
56+
```
57+
58+
That is to grant the role of `appinsights-k8s-property-reader` to the default service account in the namespace of `ai-k8s-demo`.
59+
60+
* If you don't want to create a Cluster Role, it is also possible to use Role and RoleBinding starting with Application Insights for Kubernetes 2.0.6+. Follow the example in [sa-role-none-cluster.yaml](./sa-role-none-cluster.yaml). In that case, you will not have node info on the telemetries.
61+
5662
* Now you can deploy it:
5763

5864
```shell
5965
kubectl create -f `sa-role.yaml`
6066
```
6167
See [sa-role.yaml](sa-role.yaml) for a full example.
6268

63-
> :warning: Check back for various permissions needed. Depends on the implementations, it may change in the over time.
69+
> :warning: Check back for various permissions needed. Depending on the properties we try to fetch, it may change over time.
6470

6571
## Ad-hoc troubleshooting for permission
6672

67-
Kubectl provides an `auth --can-i` sub command for troubleshooting permissions. It supports impersonate the service account. We can leverage it for permission troubleshooting, for example:
73+
Kubectl provides an `auth --can-i` subcommand for troubleshooting permissions. It supports impersonating the service account. We can leverage it for permission troubleshooting, for example:
6874

6975
```shell
7076
kubectl auth can-i list pod --namespace ai-k8s-demo --as system:serviceaccount:ai-k8s-demo:default
@@ -84,7 +90,7 @@ yes
8490

8591
## Use SubjectAccessReview
8692

87-
Kubernetes also provides `SubjectAccessReview` to check permission for given user on target resource.
93+
Kubernetes also provides `SubjectAccessReview` to check permission for a given user on the target resource.
8894

8995
### Basic usage
9096

@@ -147,7 +153,7 @@ Kubernetes also provides `SubjectAccessReview` to check permission for given use
147153
* [subject-access-review-key.yaml](./subject-access-review-key.yaml): a subset of permissions for probing the RBAC settings.
148154
* [subject-access-review-full.yaml](./subject-access-review-full.yaml): a full list of all permissions needed for RBAC settings in case any specific permission is missing.
149155

150-
Let us know if there's questions, suggestions by filing [issues](https://github.com/microsoft/ApplicationInsights-Kubernetes/issues).
156+
Let us know if there are questions or suggestions by filing [issues](https://github.com/microsoft/ApplicationInsights-Kubernetes/issues).
151157

152158
## References
153159

docs/sa-role-none-cluster.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
kind: Role
2+
apiVersion: rbac.authorization.k8s.io/v1
3+
metadata:
4+
namespace: ai-k8s-demo
5+
name: appinsights-k8s-property-reader-role
6+
rules:
7+
- apiGroups: ["", "apps"]
8+
resources: ["pods", "replicasets", "deployments"]
9+
verbs: ["get", "list"]
10+
---
11+
# Actual RoleBinding
12+
apiVersion: rbac.authorization.k8s.io/v1
13+
kind: RoleBinding
14+
metadata:
15+
name: appinsights-k8s-property-reader-binding
16+
namespace: ai-k8s-demo
17+
subjects:
18+
- kind: ServiceAccount
19+
name: default
20+
namespace: ai-k8s-demo
21+
roleRef:
22+
kind: Role
23+
name: appinsights-k8s-property-reader-role
24+
apiGroup: rbac.authorization.k8s.io

src/ApplicationInsights.Kubernetes/IK8sQueryClient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace Microsoft.ApplicationInsights.Kubernetes
1111
internal interface IK8sQueryClient : IDisposable
1212
{
1313
Task<IEnumerable<K8sDeployment>> GetDeploymentsAsync(CancellationToken cancellationToken);
14-
Task<IEnumerable<K8sNode>> GetNodesAsync(CancellationToken cancellationToken);
14+
Task<IEnumerable<K8sNode>> GetNodesAsync(bool ignoreForbiddenException, CancellationToken cancellationToken);
1515
Task<IEnumerable<K8sPod>> GetPodsAsync(CancellationToken cancellationToken);
1616
Task<K8sPod?> GetPodAsync(string podName, CancellationToken cancellationToken);
1717
Task<IEnumerable<K8sReplicaSet>> GetReplicasAsync(CancellationToken cancellationToken);

src/ApplicationInsights.Kubernetes/K8sEnvironmentFactory.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,14 +106,15 @@ public K8sEnvironmentFactory(
106106

107107
if (instance.myPod is not null)
108108
{
109-
IEnumerable<K8sNode> nodeList = await queryClient.GetNodesAsync(cancellationToken).ConfigureAwait(false);
109+
IEnumerable<K8sNode> nodeList = await queryClient.GetNodesAsync(ignoreForbiddenException: true, cancellationToken: cancellationToken).ConfigureAwait(false);
110110
string nodeName = instance.myPod.Spec.NodeName;
111-
if (!string.IsNullOrEmpty(nodeName))
111+
if (!string.IsNullOrEmpty(nodeName) && nodeList.Any())
112112
{
113113
instance.myNode = nodeList.FirstOrDefault(node => string.Equals(node.Metadata?.Name, nodeName, StringComparison.OrdinalIgnoreCase));
114114
}
115115
}
116116
}
117+
117118
return instance;
118119
}
119120
catch (UnauthorizedAccessException ex)

src/ApplicationInsights.Kubernetes/K8sQueryClient.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,27 @@ public Task<IEnumerable<K8sDeployment>> GetDeploymentsAsync(CancellationToken ca
9191
#endregion
9292

9393
#region Node
94-
public Task<IEnumerable<K8sNode>> GetNodesAsync(CancellationToken cancellationToken)
94+
public async Task<IEnumerable<K8sNode>> GetNodesAsync(bool ignoreForbiddenException, CancellationToken cancellationToken)
9595
{
9696
EnsureNotDisposed();
9797

98-
string url = Invariant($"api/v1/nodes");
99-
return GetAllItemsAsync<K8sNode>(url, cancellationToken);
98+
try
99+
{
100+
string url = Invariant($"api/v1/nodes");
101+
return await GetAllItemsAsync<K8sNode>(url, cancellationToken).ConfigureAwait(false);
102+
}
103+
catch (UnauthorizedAccessException ex)
104+
{
105+
// Prefer ignoring the unauthorized access exception
106+
if (ignoreForbiddenException)
107+
{
108+
_logger.LogDebug(ex.Message);
109+
_logger.LogTrace(ex.ToString());
110+
return Enumerable.Empty<K8sNode>();
111+
}
112+
// else
113+
throw;
114+
}
100115
}
101116
#endregion
102117

src/ApplicationInsights.Kubernetes/TelemetryInitializers/KubernetesTelemetryInitializer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,8 @@ private void SetCustomDimensions(ISupportProperties telemetry)
159159
SetCustomDimension(telemetry, Deployment.Name, this._k8sEnvironment.DeploymentName, isValueOptional: true);
160160

161161
// Node
162-
SetCustomDimension(telemetry, Node.ID, this._k8sEnvironment.NodeUid);
163-
SetCustomDimension(telemetry, Node.Name, this._k8sEnvironment.NodeName);
162+
SetCustomDimension(telemetry, Node.ID, this._k8sEnvironment.NodeUid, isValueOptional: true);
163+
SetCustomDimension(telemetry, Node.Name, this._k8sEnvironment.NodeName, isValueOptional: true);
164164
}
165165

166166
private void SetCustomDimension(ISupportProperties telemetry, string key, string value, bool isValueOptional = false)

tests/UnitTests/K8sQueryClientTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ public async Task GetNodesAsyncShouldHitsTheUri()
215215
httpClientMock.Setup(httpClient => httpClient.SendAsync(It.IsAny<HttpRequestMessage>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(response));
216216
using (K8sQueryClient target = new K8sQueryClient(httpClientMock.Object))
217217
{
218-
await target.GetNodesAsync(cancellationToken: default);
218+
await target.GetNodesAsync(ignoreForbiddenException: true, cancellationToken: default);
219219
}
220220

221221
httpClientMock.Verify(mock => mock.SendAsync(It.Is<HttpRequestMessage>(m => m.RequestUri.AbsoluteUri.Equals("https://baseaddress/api/v1/nodes")), It.IsAny<CancellationToken>()), Times.Once);
@@ -245,7 +245,7 @@ public async Task GetNodesAsyncShouldReturnsMultipleNodes()
245245

246246
using (K8sQueryClient target = new K8sQueryClient(httpClientMock.Object))
247247
{
248-
IEnumerable<K8sNode> result = await target.GetNodesAsync(cancellationToken: default);
248+
IEnumerable<K8sNode> result = await target.GetNodesAsync(ignoreForbiddenException: true, cancellationToken: default);
249249

250250
Assert.NotNull(result);
251251
Assert.Equal(2, result.Count());

tests/UnitTests/KubernetesTelemetryInitializerTests.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,17 +144,18 @@ public void InitializeWithEmptyForRequiredPropertyDoesLogError()
144144
var envMock = new Mock<IK8sEnvironment>();
145145
envMock.Setup(env => env.ContainerName).Returns("Hello RoleName");
146146

147+
// These 2 properties are required.
148+
envMock.Setup(env => env.PodID).Returns<string>(null);
149+
envMock.Setup(env => env.PodName).Returns<string>(null);
150+
147151
envMock.Setup(env => env.ContainerID).Returns("Cid");
148152
envMock.Setup(env => env.ContainerName).Returns("CName");
149-
envMock.Setup(env => env.PodID).Returns("Pid");
150-
envMock.Setup(env => env.PodName).Returns("PName");
151153
envMock.Setup(env => env.PodLabels).Returns("PLabels");
152154
envMock.Setup(env => env.ReplicaSetUid).Returns<string>(null);
153155
envMock.Setup(env => env.ReplicaSetName).Returns<string>(null);
154156
envMock.Setup(env => env.DeploymentUid).Returns<string>(null);
155157
envMock.Setup(env => env.DeploymentName).Returns<string>(null);
156158
envMock.Setup(env => env.PodNamespace).Returns<string>(null);
157-
// These 2 properties are required.
158159
envMock.Setup(env => env.NodeUid).Returns<string>(null);
159160
envMock.Setup(env => env.NodeName).Returns<string>(null);
160161

0 commit comments

Comments
 (0)