candiddev/vault-plugin-secrets-wireguard

Better support for non-mesh groups

Opened this issue · 13 comments

This plugin currently generates configs that include all peers. When these peers don't have endpoints, and the peer that the config is being generated for doesn't have an endpoint, it doesn't make sense to have them as a peer. It doesn't hurt anything, it just registers a bunch of unreachable peers.

We should add a field to groups called "mesh" and default it to true. If false, change the templates to not render peers if they don't have a port specified and the peer being rendered doesn't either--it should only have peers with endpoints/ports.

Could this be done without configuration? When rendering the config for a peer without an Endpoint, skip all other peers which don’t have an Endpoint. This would allow “partial mesh” networks.

Yea this is probably a bug to be honest. If a peer doesn't have an endpoint, it shouldn't add peers that also don't have endpoints.

I spent a few hours today playing with the project and have refined my thoughts a little bit on this. First, my use case is primarily a hub-and-spoke topology, so it doesn’t fit in with the default use case.

I can’t use the wg-quick template for the hub node because I need to add custom PostUp and PostDown lines to the template. I don’t want to use the wg-quick template for the roaming nodes because of the useless Peer sections this would add.

I would have been suited by writing custom templates, but unfortunately it doesn’t seem to be possible to do this securely. Specifically, I can write a template something like this to mimic the default template, which is great, because it gives me all the flexibility I could want. However, this approach means that I have to grant access to “wireguard/groups/mygroup/*” to all peers, and that means that any peer can read the private key for any other peer.

Therefore, I propose that the peer path be split into a “public” and “private” path. This way, I can simply write my own template, and I can grant a node the ability to get its private key and the public keys of all peers in the group, without also granting the peer the ability to get all private keys in the group.

{{- with secret "wireguard/groups/mygroup/peer1" -}}
[Interface]
Address = {{ .Data.ip }}
ListenPort = {{ .Data.port }}
PrivateKey = {{ .Data.private_key }}

{{ end }}

{{- range secrets "wireguard/groups/mygroup/" -}}
{{- with secret (printf "wireguard/groups/mygroup/%s" .) -}}
{{- if ne .Data.name "peer1" -}}
[Peer]
PublicKey = {{ .Data.public_key }}
AllowedIPs = {{ range $i, $ip := .Data.allowed_ips }}{{ if ne $i 0 }},{{ end }}{{$ip}}{{ end }}

{{ end -}}
{{- end -}}
{{- end -}}

I don't give my nodes blanket access, I use Vault policy templates: https://learn.hashicorp.com/tutorials/vault/policy-templating#create-templated-acl-policies. Can you use that?

The policy template allows you to restrict access to a particular path, but in this plugin the path is just a blob which contains the private key and public key. I don’t believe it’s possible to restrict further than the path for reads… It would be possible to prevent writes to e.g. the private key using the denied_parameters option, but if this applies to reads I don’t know how to make it work.

[append]
Seems not: hashicorp/vault#15951

I use something like this:

path "wireguard/groups/mygroup/{{ identity.entity.name }}" {
  capabilities = [ "create" ]
}


path "wireguard/groups/mygroup/{{ identity.entity.name }}/wg-quick" {
  capabilities = [ "read" ]
}

Oh I misread your comment above, you don't want to use the wg-quick endpoint. What about having a wireguard/groups/mygroup/peers endpoint that you can read to get a list of peers and then iterate if necessary?

Exactly, and I can actually do that, as demonstrated in the template I showed. But the problem is that I need to create a policy which can read the public_key of a peer but not the private_key of that peer. I don’t believe this is possible without creating a path endpoint. To be explicit:

  • list wireguard/groups/mygroup will list all peers - this already works
  • get wireguard/groups/mygroup/peer1 will return the public AND private keys for peer1 - this is a problem
  • suggestion: get wireguard/groups/mygroup/peer1/public returns everything in the parent path except the private_key is filtered out

I think it would be easier for you to get all the peers from the peer path, that way your policy is all scoped to the peer name. Why do you want to iterate over peer paths like that?

Also there could be a group property for a custom wg-quick template (or properties for setting certain fields?)

If you’re suggesting that the “read wireguard/groups/mygroup/peers” would return an array of all of the peers with all of the information except the private keys, that would also work well (and would be easier to write a template). 🙂

A group-level property for a custom wg-quick template would not work for me because one oft he peers has a separate config (I mean, I would use that for all the roaming peers, but the hub peer would still need a separate config).

I was thinking, given peer1/2/3 in mygroup, you could read (or list?) /mygroup/peer1/peers and get an object with a property peers that is an array of peer objects containing endpoint, public key, and allowed IPs.

Yeah, something like that would be perfect for my use case.

For now I’ll make do with the insecure solution, but it would be an improvement to switch to the path you are describing.