-
Notifications
You must be signed in to change notification settings - Fork 143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support of secrets for autodiscovery scenarios in kubernetes environments #2200
Comments
@gizas The
The controller passes the
This means that unlike other providers (like the So the point that you are raising here indicates that maybe we cannot rely on the controller to do this evaluation when a secret reference is coming from the hints mechansim. This is how this would look like: Having a hints like: kind: Pod
metadata:
name: redis
annotations:
co.elastic.hints/package: redis
co.elastic.hints/data_streams: info
co.elastic.hints/host: '${kubernetes.pod.ip}:6379'
co.elastic.hints/info.period: 1m
co.elastic.hints/info.password: ${kubernetes_secrets.default.redispass.value} So the hints mechanism would produce an input like the following out of the hints' template: inputs:
- name: redis/metrics-redis
streams:
-data_stream:
dataset: redis.info
type: metrics
hosts:
- 167.13.5.1:6379
idle_timeout: 20s
maxconn: 10
metricsets:
- info
network: tcp
password: ${kubernetes_secrets.default.redispass.value}
period: '1m'
type: redis/metrics
use_output: default So at this point the first variable evaluation has taken place and the hints' placeholders have been filled properly, however the policy still has a dynamic variable reference cause the password's hint provided a reference to another dynamic variable which is In this regard I would propose to directly call the elastic-agent/internal/pkg/composable/providers/kubernetes/hints.go Lines 119 to 120 in 8351358
${kubernetes_secrets.*} pattern.
Ofc, this should be configurable so as to allow users to disable this functionality (for security reasons) and also make sure that the defined secret belongs to the same namespace with the target workload. So for instance if we deploy a Redis Pod in This is what I mean by |
Thanks for the clarification! So the second validation (if possible) is the problem here. Now in our docs we have co.elastic.hints/password (not co.elastic.hints/info.password) and I guess we dont handle it at all. Also because we touch the secret handling in this story, I bring to the attention this https://kubernetes.io/docs/tasks/administer-cluster/kms-provider/, that is available since 1.25 version. Maybe can also help us keep secrets encrypted at rest. And as long as we touch the K8s API call, we can test if we can support it additionally |
I am confused here, I read the whole flow and still a little lost. Why is |
You can refer to setting of a specific data_stream if you prefix the the setting with the name of the data_stream. So yes |
@blakerouse would sth like |
For reference I have run some more detailed tests that illustrate the point. Using the following standalone input policy: inputs:
- name: kubernetes-node-metrics
type: kubernetes/metrics
use_output: default
meta:
package:
name: kubernetes
version: 1.9.0
data_stream:
namespace: default
streams:
- data_stream:
dataset: kubernetes.node
type: metrics
metricsets:
- node
add_metadata: true
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
hosts:
- 'https://${env.NODE_NAME}:10250'
period: 10s
ssl.verification_mode: none
- data_stream:
dataset: kubernetes.pod
type: metrics
metricsets:
- pod
add_metadata: true
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
hosts:
- 'https://${env.NODE_NAME}:10250'
period: 10s
ssl.verification_mode: ${kubernetes_secrets.default.redispass.value} I deploy a secret accordingly: cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: redispass
type: Opaque
data:
value: $(echo -n "myred1sp@ss" | base64)
EOF And then I deploy a target Redis Pod: apiVersion: v1
kind: Pod
metadata:
name: redis
annotations:
co.elastic.hints/package: redis
co.elastic.hints/data_streams: info
co.elastic.hints/host: '${kubernetes.pod.ip}:6379'
co.elastic.hints/info.period: 1m
co.elastic.hints/info.password: ${kubernetes_secrets.default.redispass.value}
labels:
k8s-app: redis
app: redis
spec:
containers:
- image: redis
imagePullPolicy: IfNotPresent
name: redis
ports:
- name: redis
containerPort: 6379
protocol: TCP
command:
- redis-server
- "--requirepass 'myred1sp@ss'" The resulted computed config is: inputs:
- data_stream:
namespace: default
meta:
package:
name: kubernetes
version: 1.9.0
name: kubernetes-node-metrics
streams:
- add_metadata: true
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
data_stream:
dataset: kubernetes.node
type: metrics
hosts:
- https://kind-control-plane:10250
metricsets:
- node
period: 10s
ssl.verification_mode: none
- add_metadata: true
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
data_stream:
dataset: kubernetes.pod
type: metrics
hosts:
- https://kind-control-plane:10250
metricsets:
- pod
period: 10s
ssl.verification_mode: myred1sp@ss
type: kubernetes/metrics
- data_stream.namespace: default
id: kubernetes-fb93b94c-852f-4c90-b352-f168f5ef1043
name: redis/metrics-redis
processors:
- add_fields:
fields:
cluster:
name: kind
url: kind-control-plane:6443
target: orchestrator
- add_fields:
fields:
labels:
app: redis
k8s-app: redis
namespace: default
namespace_labels:
kubernetes_io/metadata_name: default
namespace_uid: a5692398-e32f-4332-979f-97f6f040faa3
node:
hostname: kind-control-plane
labels:
beta_kubernetes_io/arch: amd64
beta_kubernetes_io/os: linux
kubernetes_io/arch: amd64
kubernetes_io/hostname: kind-control-plane
kubernetes_io/os: linux
node-role_kubernetes_io/control-plane: ""
node_kubernetes_io/exclude-from-external-load-balancers: ""
name: kind-control-plane
uid: 1e3c36d2-f3e7-4d1f-ba66-75f72102cf22
pod:
ip: 10.244.0.5
name: redis
uid: fb93b94c-852f-4c90-b352-f168f5ef1043
target: kubernetes
streams:
- data_stream:
dataset: redis.info
type: metrics
hosts:
- 10.244.0.5:6379
idle_timeout: 20s
maxconn: 10
metricsets:
- info
network: tcp
password: ""
period: 1m
type: redis/metrics So the observations here are:
@blakerouse to clarify your question more: in elastic-agent/internal/pkg/composable/providers/kubernetes/hints.go Lines 119 to 120 in 8351358
|
Okay it is making more sense. I was not familiar with how hints where implemented in the Elastic Agent. After reviewing it and reviewing your comment I have a better understanding.
The code above makes sense because So doing a raw second pass would probably not provide a result that would be expected. One option is to use a We might need to do a second pass, but we need to be very specific on the matching regex to ensure that a second pass would not overwrite any escaped variables. We might want the hints provide to take say any non-matching |
This looks a bit hacky to me tbh, and would imply an extra difficulty to debug and support this. How we would ensure that our users would be able to troubleshoot such a case when it involves 2 passes of variable evaluation. In this regard I would lean towards avoiding to go down the path of the 2 passes and for secrets do the evaluation at first place when the proper hint is detected. I don't think it's a serious issue if the secret is changed since it would be expected to affect all of its consumers. The same will happen in general in k8s if the secrets was mounted inside a Pod. Having the implementation for the secrets inside the hints mechanism would also reduce the implementation time/overhead and would be easier to support as I mentioned already. @gizas @MichaelKatsoulis what do you think here? |
This is not a serious problem if it is documented. I would avoid requesting from user counter-intuitive things like |
Also +1 to this if we can avoid it. I think the maintenance cost of this will be too high. It suggests there is something we need to rethink in the way we do variable substitution if the need for this type of thing becomes a recurring problem. #2177 seems related here. |
I would agree that if we can avoid From what is documented above I would suggest:
We should document that this way all secrets are read on start up time of container and in case of expiration user needs to restart and force reading. But we should be clear on this |
First the In the case of the I think we should strive to use existing AST and variable replacement instead of adding something into the hints provider. |
Still hard to troubleshoot and follow the execution flow though. Even today, it is sometimes hard for our users or support-team to troubleshoot the variable resolution part. I assume that introducing a 2nd pass would make it even more hard.
Exposing the whole set of the providers' variables to the hints mechanism would introduce some security concerns. This would mean that everyone with access to to deploy Pods (on a node) would potentially be able to access environment variables, local provider's variables etc. I already see that some sensitive data like ES username/password are set in env vars: https://github.com/elastic/elastic-agent/blob/main/deploy/kubernetes/elastic-agent-standalone-kubernetes.yaml#L696-L700. This security aspect is something that was concerning in the past and hence I would be hesitant to provide full access to the hints mechanism. For k8s secrets we have defined a specific strategy of what to allow and what not but we cannot do the same for the whole set of the providers. In this regard, I would start with only making the k8s secrets natively available within the kubernetes provider (hints mechanism) and if there are requests in the future we can reconsider what is the best way to support additional providers (if we actually want it). That's my proposal for now (time-wise and support-wise).
In that case we would talk about 2 different issues: one on Agent's core AST to support the second pass and another one to implement the |
After discussion with the @cmacknz we have decided that the second pass approach would be a better and more future proof way of handling this issue. The reasons are as follows:
How it will be implemented is a little different then how I described previously:
This removes the need for The above relates to this other issue - #2177 |
Ok, how the security concerns would be covered in that case? If that's the way forward, are you going to create a different generic issue for the 2-step variable resolution support in Agent's core side? |
@blakerouse my understanding is that we will ask the users to fill in secrtes with $$ . Afterall, even with your solution you need to search and replace. So why dont you search initially for secrtes.kubernetes and do the replacement and then replace rest of vars?
Maybe I am missing something here, can you elaborate on this?
For me the support of new secrets will be just the match of words like secrtes.kuberntes, secrets.vault, secrtets.aws etc
Also here the problem is slightly different on your side and I think we try here to solve two problems with one solution. Just my idea here is that escaping characters for users mean usage of symbol \ . Long story short, lets have a second round of thoughts here if you consider that is meaningful. Of course we depend on your implementation, if you decide to go with way proposed, we will follow up with the implementation of hints once you are done |
No they will not need to use
The reasons where provided above.
The reasons say way this becomes a bolt on.
Again this becomes a bolt on and now every time we add a new secrets provider we need to add it in 2 places. We should only need to register it and it just work inside of the Elastic Agent.
Not trying to solve two problems with one solution. We have a problem that needs to be handled no matter what and that problem causes the solution in this issue to be something that needs to be thought about.
This solution requires nothing to be implemented in hints, it just works. Which is why we lean on that solution. Lets say we do hints for another type of provider, it will just work in that new provider, no need to add something specific to hints in that case. |
@blakerouse @cmacknz I don't see any of the security concerns mentioned at #2200 (comment) being covered in the solution you propose. |
Sorry, that's confusing to me that they will not need to use Could you provide a more detailed example here? How the hints in the annotations would look like and how this would be handled under the hood (and by which component)? |
The hints in will be the same as your previous comment above, like the following:
The 2 stages will work like the following:
|
So @blakerouse will it be the hints mechanism within the |
@ChrsMark No. The provider will not need to do any transformation. It will remain as it is today. Pass 2 is done after the providers, so it will transform any remaining I don't see there even needed to be a change in any provider for this to work. |
So @blakerouse how we will end up with the double |
My understanding from reading this is that in the hints case you don't ever have The The idea of the second pass here handles two separate cases 1) substituting If that is incorrect and doesn't clarify things, we should setup a quick meeting to clarify the outcome here. |
I will try to follow the flow:
Is that accurate? |
@cmacknz You are correct. |
Second pass is always triggered and second pass substitutes both
The kubernetes provider does not need to be changed at all, as I stated above. |
Thanks @cmacknz for elaborating on this. The mention to the
and properly replace the leftover So I think we are aligned here. Last but not least, I still see a potential security issue by allowing hints to have access to all the providers through the second pass but we can deal with this it once we have the 2 passes evaluation implemented and see if we need to adjust hints so as to handle accesses better. |
I think we can easily add the ability for a provider to say it should not be present in second pass. |
We need to verify and document that we can provide similar experience with Agent with the one we had in Beats.
Required actions:
kubernetes_secrets
provider in template based autodiscovery. We need to update https://www.elastic.co/guide/en/fleet/master/kubernetes_secrets-provider.html and https://www.elastic.co/guide/en/fleet/8.6/conditions-based-autodiscover.html properly.kubernetes_secrets
) or by directly doing what the provider does accessing the k8s API when a secret reference is identified. I would lean towards the latter option which by-pass the second dynamic variable evaluation pass (which might not be possible) and directly handles the k8s API access. The provider's code can be re-used.cc: @gizas @mlunadia @rameshelastic @ruflin
Useful links:
The text was updated successfully, but these errors were encountered: